/*************************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2001-2004.
-------------------------------------------------------------------------
$Id: AssetViewer.h  ,v 1.1 2008/08/19 12:54:41 PauloZaffari Exp wwwrun $
$DateTime$
Description:
This file declares a control which objective is to display 
multiple Assets allowing selection and preview of such things.
It also must handle scrolling and changes in the Asset 
cell display size.
-------------------------------------------------------------------------
History:
- 19:08:2008   12:54 : Created by Paulo Zaffari
- 11:03:2010   17:34 : Nicusor Nedelcu - refactored

*************************************************************************/

#include "StdAfx.h"
#include "AssetViewer.h"
#include "ImageExtensionHelper.h"
#include "Include/ITextureDatabaseUpdater.h"
#include "Include/IAssetDisplay.h"
#include "Include/IAssetDisplayDatabase.h"
#include "GameEngine.h"
#include "ViewManager.h"
#include "Asset Browser/AssetBrowserPreviewDlg.h"
#include "Asset Browser/AssetMetaDataFileDB.h"
#include "Dialogs/Generic/StringInputDialog.h"

//--- Constants / defines

//! timer id for smooth panning with mouse right-click-release dragging
#define ID_ASSET_VIEWER_TIMER_SMOOTH_PANNING 2
//! timer id for cache assets which cannot be cached inside a separate thread
#define ID_ASSET_VIEWER_TIMER_CACHE_ASSET 3
//! timer for warning blinking on asset thumbs, it will toggle a boolean, m_bWarningBlinkToggler, and redraw asset control
#define ID_ASSET_VIEWER_TIMER_WARNING_BLINK_TOGGLER 4
//! timer for when user selects an asset in the list view, and then the viewer must animate, attract user attention to the selected thumbnail
#define ID_ASSET_VIEWER_TIMER_THUMB_FOCUS_ANIMATION 5
//! window control id to render assets in
#define ID_ASSET_VIEWER_THUMB_RENDER_WINDOW 1

namespace AssetViewer
{
	// this is the delay for the timer which caches assets that cannot be cached on a separate thread (ex.: models, characters)
	const int kNonThreadCacheTimerDelay = 80;
	// this is the loop sleep time (msec) for the caching thread when waiting for the metadata db to be loaded
	const int kThreadSleepTimeWaitLoadAndUpdateDone = 100;
	// this is the delay for the timer which pans/scrolls the canvas smoothly
	const UINT kSmoothPanningTimerDelay = USER_TIMER_MINIMUM;
	// this is the delay for the timer which toggles the warning blinker
	const UINT kWarningBlinkerTimerDelay = 500;
	// timer delay used to animate a rectangle to focus a thumb when selected in list view
	const UINT kThumbFocusAnimationTimerDelay = USER_TIMER_MINIMUM;
	// the maximum number of thumbnails that can be stored at once in the asset thumb queue
	const UINT kMaxThumbsCacheAssetCount = 500;
	// boosts the thumb smooth panning delta (multiplier), so it can smooth scroll/pan more/less
	const float kSmoothPanningDeltaBoost = 2;
	// interactive render timer delay
	const UINT kInteractiveRenderTimerDelay = USER_TIMER_MINIMUM;
	// this is the minimum offset on X and Y the mouse can be dragged over an asset selection, before it is considered a rectangle selection operation
	const UINT kMinimumSelectionDraggingOffset = 15;
	// this is the minimum offset on X and Y the mouse can be dragged over on right click and drag,
	// before it starts to pan the canvas, else it will show the asset context menu
	const UINT kMinimumStartPanningOffset = 1;
	// this is the number multiplied by the current smooth panning delta, to decrease it until in reaches near 0
	const float kSmoothPanning_SlowdownMultiplier = 0.9f;
	const float kSmoothPanning_Speed = 0.01f;

	// the key to be pressed to show
	const UINT	kPreviewShortcutKey = '1';

	const int		kThumb_VerticalSpacing = 35;
	const int		kThumb_HorizontalSpacing = 20;
	const UINT	kSmallThumbSize = 64;
	const UINT	kBigThumbSize = 512;

	const int kTooltip_Margin = 20;
	const int kTooltip_MinWidth = 200;
	const int kTooltip_MinHeight = 80;
	const int kTooltip_TitleContentSpacing = 5;
	const int kTooltip_BorderWidth = 2;
	const int kTooltip_BorderCornerSize = 0;
	const COLORREF kTooltip_BorderColor = RGB( 10, 10, 10 );
	const COLORREF kTooltip_FilenameShadowColor = RGB( 0, 0, 0 );
	const COLORREF kTooltip_FilenameColor = RGB( 255, 255, 0 );
	const COLORREF kSelectionDragLineColor = RGB( 255, 255, 100 );
	const COLORREF kLabelColor = RGB( 255, 255, 100 );
	const COLORREF kLabelBoxBackColor = RGB( 100, 100, 100 );
	const COLORREF kLabelBoxLineColor = RGB( 255, 255, 0 );
	const COLORREF kLabelBoxSelectedBackColor = RGB( 20, 116, 173 );
	const COLORREF kLabelBoxNonSelectedBackColor = RGB( 120, 120, 120 );
	const COLORREF kLabelBoxSelectedLineColor = RGB( 110, 110, 110 );
	const COLORREF kLabelBoxNonSelectedLineColor = RGB( 40, 40, 40 );
	const COLORREF kLabelTextColor = RGB( 255, 255, 255 );
	const COLORREF kOneLineInfoTextColor = RGB( 255, 155, 55 );
	const COLORREF kThumbContainerHoverLineColor = RGB( 255, 255, 255 );
	const COLORREF kThumbContainerLineColor = RGB( 60, 60, 60 );
	const COLORREF kInfoTextColor = RGB( 0, 255, 255 );
	const COLORREF kThumbContainerWarningBackColor = RGB( 255, 50, 0 );
	const COLORREF kThumbContainerBackColor = RGB( 50, 50, 50 );

	const int kLabelFontSize_Small = 60;
	const int kLabelFontSize_Normal = 80;
	const int kLabelFontSize_Large = 120;

	const int kOneLinerInfoFontSize_Small = 55;
	const int kOneLinerInfoFontSize_Normal = 70;
	const int kOneLinerInfoFontSize_Large = 110;

	const int kInfoTitle_FontSize = 85;
	const int kInfoText_FontSize = 75;

	const int		kMiniThumbButton_VerticalSpacing = 4;
	const int		kMiniThumbButton_Width = 16;
	const int		kMiniThumbButton_Height = 16;
	const BYTE	kMiniThumbButton_ActiveAlpha = 200;
	const BYTE	kMiniThumbButton_HoveredAlpha = 255;
	const BYTE	kMiniThumbButton_InactiveAlpha = 40;
	const COLORREF kMiniThumbButton_TextColor = RGB(0, 0, 0);
	const COLORREF kMiniThumbButton_BorderColor = RGB(22, 22, 22);
	const COLORREF kMiniThumbButton_BackColor = RGB(255, 255, 100);

	const UINT	kCachingInfoProgressBar_Height = 30;

	const char* kThumbShadowBmpFilename = "Editor/UI/Icons/AssetBrowserThumbShadow.png";
	const char* kThumbLoadingBmpFilename = "Editor/UI/Icons/AssetBrowserThumbLoading.png";
	const char* kThumbInvalidAssetBmpFilename = "Editor/UI/Icons/AssetBrowserThumbInvalid.png";
	const char* kBackgroundBmpFilename = "Editor/UI/Icons/AssetBrowserBack.png";
	const char* kEditTagsBmpFilename = "Editor/UI/Icons/AssetTag.png";
	const char* kToggleFavBmpFilename = "Editor/UI/Icons/AssetFav.png";
};

//---

CryMutex			CAssetViewer::s_lockAll;
SAssetField*	CAssetViewer::s_pSortField = NULL;
bool					CAssetViewer::s_bSortDescending = false;

BEGIN_MESSAGE_MAP(CAssetViewer, CScrollableWindow)
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONDOWN()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_PAINT()
	ON_WM_SIZE()
	ON_WM_VSCROLL()
	ON_WM_MOUSEWHEEL()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONUP()
	ON_WM_MBUTTONDOWN()
	ON_WM_MBUTTONUP()
	ON_WM_KEYUP()
	ON_WM_TIMER()
	ON_WM_DESTROY()
	ON_WM_KEYDOWN()
	ON_WM_SETCURSOR()
END_MESSAGE_MAP()

CAssetViewer::CAssetViewer()
{
	m_nItemHorizontalSpacing = AssetViewer::kThumb_HorizontalSpacing;
	m_nItemVerticalSpacing = AssetViewer::kThumb_VerticalSpacing;
	m_nAssetThumbSize = gSettings.sAssetBrowserSettings.nThumbSize;
	m_bMouseLeftButtonDown = false;
	m_bMouseRightButtonDown = false;
	m_bDragging = false;
	m_bDraggingAssetFromBrowser = false;
	m_bDraggingCreatedAssetInWorld = false;
	m_pDraggedAsset = NULL;
	m_pDropInViewport = NULL;
	m_oSelectionRect.SetRect( 0, 0, 0, 0 );
	m_oClientRect.SetRect( 0, 0, 0, 0 );
	m_nYOffset = 0;
	m_nIdealClientWidth = 0;
	m_nIdealClientHeight = 0;
	m_poEnsureVisible = NULL;
	m_piRenderer = gEnv->pRenderer;
	m_bMustRedraw = false;

	m_thumbShadowBmp.Load( AssetViewer::kThumbShadowBmpFilename, true );
	m_thumbLoadingBmp.Load( AssetViewer::kThumbLoadingBmpFilename, true );
	m_thumbInvalidAssetBmp.Load( AssetViewer::kThumbInvalidAssetBmpFilename, true );
	m_backgroundBmp.Load( AssetViewer::kBackgroundBmpFilename, true );

	m_hDraggableAssetIcon = LoadIcon( AfxGetApp()->m_hInstance, MAKEINTRESOURCE( IDI_DRAGGABLE_ASSET )  );
	m_hWarningSmallIcon = LoadIcon( AfxGetApp()->m_hInstance, MAKEINTRESOURCE( IDI_WARNING_SMALL )  );
	
	m_backBrush.CreatePatternBrush( &m_backgroundBmp.GetBitmap() );
	m_fontInfoTitle.CreatePointFont( AssetViewer::kInfoTitle_FontSize, "Arial Bold" );
	m_fontInfoText.CreatePointFont( AssetViewer::kInfoText_FontSize, "Arial" );

	m_poClickedAsset = NULL;
	m_poHoveredAsset = NULL;
	m_smoothPanLastDelta = 0;
	m_smoothPanLeftAmount = 0;
	m_nTotalAssets = 0;
	m_thumbFocusAnimationTime = 1;
	m_bCachingInProgress = false;
	m_bCancelCachingButtonHovered = false;
	m_cachingCurrentCount = m_cachingTotalCount = 0;
	m_bCancelCachingButtonPressed = false;
	m_pHoveredThumbButtonAsset = NULL;
	m_nHoveredThumbButtonIndex = 0;

	m_thumbMiniButtons.push_back( new CThumbMiniButton( OnThumbButtonClickTags, "Edit Tags", AssetViewer::kEditTagsBmpFilename ) );
	m_thumbMiniButtons.push_back( new CThumbMiniButton( OnThumbButtonClickFavourite, "Toggle Favourite", AssetViewer::kToggleFavBmpFilename ) );

	SetAutoScrollWindowFlag( false );
}

void CAssetViewer::OnDestroy()
{
	KillTimer( ID_ASSET_VIEWER_TIMER_SMOOTH_PANNING );
}

CAssetViewer::~CAssetViewer()
{
	if (IsStarted() || IsRunning())
	{
		Stop();
		WaitForThread();
	}

	for( size_t i = 0, iCount = m_thumbMiniButtons.size(); i < iCount; ++i )
	{
		delete m_thumbMiniButtons[i];
	}

	m_thumbMiniButtons.clear();

	GetIEditor()->UnregisterNotifyListener( this );
	m_backBrush.DeleteObject();
	m_fontLabel.DeleteObject();
	m_fontOneLinerInfo.DeleteObject();
	m_fontInfoText.DeleteObject();
	m_fontInfoTitle.DeleteObject();
	m_thumbShadowBmp.Free();
	m_thumbLoadingBmp.Free();
	m_thumbInvalidAssetBmp.Free();
	m_backgroundBmp.Free();
	
	if( m_piRenderer )
	{
		m_piRenderer->DeleteContext( m_wndAssetThumbRender.GetSafeHwnd() );
	}

	m_wndAssetThumbRender.DestroyWindow();
}

void CAssetViewer::FreeData()
{
	m_nTotalAssets = 0;
	m_cPendingCacheAssets.clear();
	m_cAssetDatabases.clear();
	m_cAssetItems.clear();
	m_cAssetDrawingCache.clear();
	m_cThumbsCache.clear();
}

bool CAssetViewer::Create( int nLeft, int nTop, UINT nWidth, UINT nHeight, CWnd* poParentWindow, UINT nId, DWORD dwStyle )
{
	bool bReturn = FALSE != CScrollableWindow::Create( NULL, "AssetViewer", dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
																										 CRect( nLeft, nTop, nWidth, nHeight ), poParentWindow, nId );

	if( !bReturn )
		return false;

	if( !m_wndAssetThumbRender.Create( NULL, "AssetThumbRender", WS_CHILD | WS_EX_TOPMOST | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
																		 CRect( 0, 0, m_nAssetThumbSize, m_nAssetThumbSize ), this, ID_ASSET_VIEWER_THUMB_RENDER_WINDOW ) )
	{
		assert( !"Could not create the asset thumb render window" );
		return false;
	}

	if( m_piRenderer->CreateContext( m_wndAssetThumbRender.GetSafeHwnd() ) )
	{
		m_piRenderer->MakeMainContextActive();
	}
	else
	{
		return false;
	}

	GetIEditor()->RegisterNotifyListener( this );
	SetTimer( ID_ASSET_VIEWER_TIMER_CACHE_ASSET, AssetViewer::kNonThreadCacheTimerDelay, NULL );
	SetTimer( ID_ASSET_VIEWER_TIMER_WARNING_BLINK_TOGGLER, AssetViewer::kWarningBlinkerTimerDelay, NULL );

	// setup thumb size, we do this here because we want the various fonts used to be created based on thumb size
	SetAssetThumbSize( gSettings.sAssetBrowserSettings.nThumbSize );

	return bReturn;
}

void CAssetViewer::LockAll()
{
	s_lockAll.Lock();
	m_oAssetDatabasesLock.Lock();
	m_oAssetItemsLock.Lock();
	s_lockAll.Unlock();
}

void CAssetViewer::UnlockAll()
{
	s_lockAll.Lock();
	m_oAssetItemsLock.Unlock();
	m_oAssetDatabasesLock.Unlock();
	s_lockAll.Unlock();
}

void CAssetViewer::LockAssetItemsArray()
{
	m_oAssetItemsLock.Lock();
}

void CAssetViewer::UnlockAssetItemsArray()
{
	m_oAssetItemsLock.Unlock();
}

void CAssetViewer::LockAssetDatabasesArray()
{
	m_oAssetDatabasesLock.Lock();
}

void CAssetViewer::UnlockAssetDatabasesArray()
{
	m_oAssetDatabasesLock.Unlock();
}

void CAssetViewer::SetAssetThumbSize( const UINT nThumbSize )
{
	m_nAssetThumbSize = nThumbSize;
	gSettings.sAssetBrowserSettings.nThumbSize = m_nAssetThumbSize;
	m_wndAssetThumbRender.SetWindowPos( 0, 0, 0, m_nAssetThumbSize, m_nAssetThumbSize, SWP_NOMOVE );

	LockAll();

	// uncache all in-view asset thumbs, we need new thumbs
	for( size_t i = 0, iCount = m_cAssetDatabases.size(); i < iCount; ++i )
	{
		for( IAssetDisplayDatabase::TFilenameAssetMap::iterator iter = m_cAssetDatabases[i]->GetAssets().begin(),
				iterEnd = m_cAssetDatabases[i]->GetAssets().end();
				iter != iterEnd; ++iter )
		{
			iter->second->UnCacheThumbnail();
		}
	}

	UnlockAll();
	RecalculateLayout();
	UpdateScrollBar();
	UpdateVisibility();

	//
	// adjust the font size based on thumb size
	//
	int labelFontSize = AssetViewer::kLabelFontSize_Normal;
	int oneLinerInfoFontSize = AssetViewer::kOneLinerInfoFontSize_Normal;

	if( m_nAssetThumbSize <= AssetViewer::kSmallThumbSize )
		labelFontSize = AssetViewer::kLabelFontSize_Small;

	if( m_nAssetThumbSize >= AssetViewer::kBigThumbSize )
		labelFontSize = AssetViewer::kLabelFontSize_Large;

	if( m_nAssetThumbSize <= AssetViewer::kSmallThumbSize )
		oneLinerInfoFontSize = AssetViewer::kOneLinerInfoFontSize_Small;

	if( m_nAssetThumbSize >= AssetViewer::kBigThumbSize )
		oneLinerInfoFontSize = AssetViewer::kOneLinerInfoFontSize_Large;

	m_fontLabel.DeleteObject();
	m_fontOneLinerInfo.DeleteObject();
	m_fontLabel.CreatePointFont( labelFontSize, "Arial" );
	m_fontOneLinerInfo.CreatePointFont( oneLinerInfoFontSize, "Arial" );

	Invalidate( FALSE );
}

UINT CAssetViewer::GetAssetThumbSize()
{
	return m_nAssetThumbSize;
}

void CAssetViewer::Run()
{	
	CryThreadSetName(-1, "CAssetViewer::Run");

	CString strFilename;
	CRect stElementRect;
	TAssetItems items;

	// in this thread we only load and prepare new thumbs
	while( IsStarted() )
	{
		if( !IsStarted() )
		{
			break;
		}

		if( !CAssetMetaDataFileDB::IsLoadAndUpdateDone() )
		{
			Sleep( AssetViewer::kThreadSleepTimeWaitLoadAndUpdateDone );
			continue;
		}

		if( !IsStarted() )
		{
			break;
		}

		m_oAssetItemsLock.Lock();
		items = m_cAssetDrawingCache;
		m_oAssetItemsLock.Unlock();

		for( size_t nCurrentItem = 0, nTotalItems = items.size(); nCurrentItem < nTotalItems; ++nCurrentItem )
		{
			if( !IsStarted() )
			{
				break;
			}

			IAssetDisplay* const cpoDatabaseItem = items[nCurrentItem];
			UINT flags = cpoDatabaseItem->GetFlags();

			if( !( flags & IAssetDisplay::eAssetFlags_ThreadCachingSupported ) ||
					 ( flags & IAssetDisplay::eAssetFlags_Cached ) ||
					 ( flags & IAssetDisplay::eAssetFlags_Invalid ) )
			{
				// friendly task
				Sleep( 1 );
				continue;
			}

			cpoDatabaseItem->Cache();
			m_bMustRedraw = true;
		}

		// be friendly with other tasks
		Sleep( 1 );
	}
}

void CAssetViewer::SetDatabases( TAssetDatabases& rcDatabases )
{
	DeselectAll();
	LockAll();
	
	m_cAssetDatabases = rcDatabases;

	for( size_t i = 0; i < m_cAssetDatabases.size(); ++i )
	{
		m_cAssetDatabases[i]->SetAssociatedViewer( this );
	}

	m_poClickedAsset = NULL;
	m_cAssetDrawingCache.clear();
	m_cPendingCacheAssets.clear();
	m_cSelectedAssets.clear();
	m_cThumbsCache.clear();
	m_poEnsureVisible = NULL;
	m_poClickedAsset = NULL;
	m_poHoveredAsset = NULL;
	m_pDraggedAsset = NULL;
	m_pDropInViewport = NULL;
	m_pDraggedAssetInstance = NULL;
	UnlockAll();

	CALL_OBSERVERS_METHOD( OnChangedPreviewedAsset( NULL ) );
}

TAssetDatabases& CAssetViewer::GetDatabases()
{
	return m_cAssetDatabases;
}

void CAssetViewer::ClearDatabases()
{
	LockAll();

	m_nTotalAssets = 0;
	m_cPendingCacheAssets.resize( 0 );
	m_cAssetDatabases.resize( 0 );
	m_cAssetItems.resize( 0 );
	m_cAssetDrawingCache.resize( 0 );
	m_cThumbsCache.resize( 0 );

	UnlockAll();
}

void CAssetViewer::WaitThreadAndFreeData()
{
	DeselectAll();

	m_oAssetItemsLock.Unlock();
	
	if (IsRunning() || IsStarted())
	{
		Stop();
		WaitForThread();
	}

	FreeData();
}

void CAssetViewer::RestartThread()
{
	if (IsStarted() || IsRunning())
	{
		DeselectAll();
		Stop();
		WaitForThread();
	}

	Start();
}

void CAssetViewer::GetSelectedItems( TAssetItems& rcpoSelectedItemArray )
{
	LockAssetItemsArray();

	for( size_t nCurrentAsset = 0, nTotalAssets = m_cAssetItems.size(); nCurrentAsset < nTotalAssets; ++nCurrentAsset )
	{
		IAssetDisplay* const cpoCurrentItem = m_cAssetItems[nCurrentAsset];

		if( cpoCurrentItem->IsFlagSet( IAssetDisplay::eAssetFlags_Selected ) )
		{
			rcpoSelectedItemArray.push_back( cpoCurrentItem );
		}
	}

	UnlockAssetItemsArray();
}

int CAssetViewer::GetFirstSelectedItemIndex()
{
	int index = -1;
	LockAssetItemsArray();

	for( size_t nCurrentAsset = 0, nTotalAssets = m_cAssetItems.size(); nCurrentAsset < nTotalAssets; ++nCurrentAsset )
	{
		IAssetDisplay* const cpoCurrentItem = m_cAssetItems[nCurrentAsset];

		if( cpoCurrentItem->IsFlagSet( IAssetDisplay::eAssetFlags_Selected ) )
		{
			index = nCurrentAsset;
			break;
		}
	}

	UnlockAssetItemsArray();

	return index;
}

void CAssetViewer::SelectAsset( IAssetDisplay* pAsset )
{
	DeselectAll();

	// if this asset is not in the current asset list, skip it
	if( m_cAssetItems.end() == std::find( m_cAssetItems.begin(), m_cAssetItems.end(), pAsset ) )
		return;
	
	if( pAsset )
	{
		LockAssetItemsArray();
		pAsset->SetFlag( IAssetDisplay::eAssetFlags_Selected, true );
		m_cSelectedAssets.push_back( pAsset );
		UnlockAssetItemsArray();
	}

	CALL_OBSERVERS_METHOD( OnChangeStatusBarInfo( m_cSelectedAssets.size(), m_cAssetItems.size(), m_nTotalAssets ) );
	CALL_OBSERVERS_METHOD( OnSelectionChanged() );
}

void CAssetViewer::SelectAssets( const TAssetItems& rSelectedItems )
{
	DeselectAll( false );
	LockAll();
	TAssetItems items;

	for( size_t i = 0, iCount = rSelectedItems.size(); i < iCount; ++i )
	{
		IAssetDisplay* const cpoCurrentItem = rSelectedItems[i];

		// if this asset is not in the current asset list, skip it
		if( m_cAssetItems.end() == std::find( m_cAssetItems.begin(), m_cAssetItems.end(), cpoCurrentItem ) )
			continue;

		cpoCurrentItem->SetFlag( IAssetDisplay::eAssetFlags_Selected, true );
		items.push_back( cpoCurrentItem );
	}

	m_cSelectedAssets = items;
	UnlockAll();
	CALL_OBSERVERS_METHOD( OnChangeStatusBarInfo( m_cSelectedAssets.size(), m_cAssetItems.size(), m_nTotalAssets ) );
	CALL_OBSERVERS_METHOD( OnSelectionChanged() );
}

void CAssetViewer::DeselectAll( bool bCallObservers )
{
	LockAssetItemsArray();

	for( size_t nCurrentAsset = 0, nTotalAssets = m_cAssetItems.size(); nCurrentAsset < nTotalAssets; ++nCurrentAsset )
	{
		IAssetDisplay* const cpoCurrentItem = m_cAssetItems[nCurrentAsset];

		cpoCurrentItem->SetFlag( IAssetDisplay::eAssetFlags_Selected, false );
	}

	m_cSelectedAssets.clear();
	UnlockAssetItemsArray();

	if( bCallObservers )
	{
		CALL_OBSERVERS_METHOD( OnChangeStatusBarInfo( m_cSelectedAssets.size(), m_cAssetItems.size(), m_nTotalAssets ) );
		CALL_OBSERVERS_METHOD( OnSelectionChanged() );
	}
}

void CAssetViewer::OnEditorNotifyEvent( EEditorNotifyEvent aEvent )
{
	switch( aEvent )
	{
		case eNotify_OnIdleUpdate:
		{
			if( m_bMustRedraw )
			{
				m_bMustRedraw = false;
				
				if( GetSafeHwnd() )
					Invalidate( FALSE );
			}

			break;
		}
	}
}

void CAssetViewer::OnSize( UINT nType, int cx, int cy )
{
	__super::OnSize( nType, cx, cy );

	GetClientRect( &m_oClientRect );
	m_cPendingCacheAssets.resize( 0 );
	
	CDC* pDC = GetDC();

	m_canvas.Create( pDC->GetSafeHdc(), cx, cy );
	ReleaseDC( pDC );
	RecalculateLayout();

	int nNewOffset = m_nYOffset;
	int nMaxScroll = m_nIdealClientHeight - m_oClientRect.Height();

	if( nMaxScroll < 0 )
	{
		nNewOffset = 0;
	}

	if( (int)m_nIdealClientHeight - nNewOffset < m_oClientRect.Height() )
	{
		nNewOffset = nMaxScroll;
	}

	if( nNewOffset < 0 )
	{
		nNewOffset = 0;
	}

	m_stDesiredClientSize.SetRect( 0, 0, m_nIdealClientWidth, m_nIdealClientHeight );
	m_nYOffset = nNewOffset;
	UpdateVisibility();
	
	if (m_poEnsureVisible && !m_oClientRect.IsRectNull())
	{
		EnsureAssetVisible(m_poEnsureVisible, false);
	}
	
	Invalidate( FALSE );
}

void CAssetViewer::OnPaint()
{
	CPaintDC dc(this);

	Draw();
}

BOOL CAssetViewer::OnEraseBkgnd( CDC* pDC )
{
	return TRUE;
}

void CAssetViewer::CheckClickedThumb( UINT nFlags, CPoint point, bool bShowInteractiveRenderWnd )
{
	CPoint stHitTestPoint( point );

	stHitTestPoint.y += m_nYOffset;
	m_poClickedAsset = NULL;

	for( size_t nCurrentAsset = 0, nTotalAssets = m_cAssetDrawingCache.size(); nCurrentAsset < nTotalAssets; ++nCurrentAsset )
	{
		IAssetDisplay* const cpoCurrentItem = m_cAssetDrawingCache[nCurrentAsset];

		if( !cpoCurrentItem->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
		{
			continue;
		}
		else
		if( cpoCurrentItem->HitTest( stHitTestPoint.x, stHitTestPoint.y ) )
		{
			m_poClickedAsset = cpoCurrentItem;
			break;
		}
		else
		{
			CRect drawingRect;

			cpoCurrentItem->GetDrawingRectangle( drawingRect );

			// expand bottom with vertical margin 
			drawingRect.bottom += m_nItemVerticalSpacing;

			if( drawingRect.PtInRect( stHitTestPoint ) )
			{
				m_poClickedAsset = cpoCurrentItem;
				break;
			}
		}
	}
}

void CAssetViewer::CheckHoveredThumb()
{
	m_poHoveredAsset = NULL;
	CPoint point;

	GetCursorPos( &point );
	ScreenToClient( &point );

	for( size_t i = 0, iCount = m_cAssetDrawingCache.size(); i < iCount; ++i )
	{
		if( !m_cAssetDrawingCache[i]->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
		{
			continue;
		}

		if( m_cAssetDrawingCache[i]->HitTest( point.x, point.y + m_nYOffset ) )
		{
			m_poHoveredAsset = m_cAssetDrawingCache[i];
			break;
		}
	}
}

void CAssetViewer::OnLButtonDown( UINT nFlags, CPoint point )
{	
	SetCapture();
	SetFocus();
	m_oSelectionRect.left = point.x;
	m_oSelectionRect.top = point.y;
	m_oSelectionRect.right = point.x;
	m_oSelectionRect.bottom = point.y;
	m_bMouseLeftButtonDown = true;
	m_bDragging = true;
	m_lastPanDragPt = point;
	m_smoothPanLastDelta = 0;
	m_bDraggingAssetFromBrowser = false;
	m_pDraggedAssetInstance = NULL;
	m_pDraggedAsset = NULL;
	m_bDraggingCreatedAssetInWorld = false;
	m_bDraggingAssetFromBrowser = false;
	m_pDropInViewport = NULL;

	KillTimer( ID_ASSET_VIEWER_TIMER_SMOOTH_PANNING );

	if( m_bCachingInProgress && m_cancelCachingButtonRect.PtInRect( point ) )
	{
		if( IDYES == AfxMessageBox( "Are you sure you want to stop the caching of asset information ?\n\nThe caching can be restarted by pressing Refresh button on the asset browser's ribbon.", MB_YESNO ) )
		{
			m_bCachingInProgress = false;
			m_bCancelCachingButtonPressed = true;
		}

		m_bDragging = false;

		return;
	}

	CheckClickedThumb( nFlags, point, true );

	//
	// if we have a clicked asset thumb, do notify it
	//
	if( m_poClickedAsset )
	{
		m_poClickedAsset->OnThumbClick( point, nFlags );
	}

	//
	// it seems we want to drag the asset in a viewport
	//
	if( m_bMouseLeftButtonDown )
	{
		if( m_poClickedAsset )
		{
			if( m_poClickedAsset->IsFlagSet( IAssetDisplay::eAssetFlags_CanBeDraggedInViewports ) )
			{
				m_bDraggingAssetFromBrowser = true;
				DeselectAll();
				m_poClickedAsset->SetFlag( IAssetDisplay::eAssetFlags_Selected, true );
				m_cSelectedAssets.push_back( m_poClickedAsset );
				::SetCursor( LoadCursor( AfxGetApp()->m_hInstance, MAKEINTRESOURCE( IDC_POINTER_DRAG_ITEM ) ) );
			}
		}
	}

	//
	// we've decided where to place the asset, so the second click will position the asset instance in the viewport and end the creation process
	//
	if( m_bDraggingCreatedAssetInWorld )
	{
		m_bDraggingCreatedAssetInWorld = false;
		RefreshAssetsUsedInLevel();
	}

	Invalidate( FALSE );
}

void CAssetViewer::OnMouseMove( UINT nFlags, CPoint point )
{
	//
	// check if mini thumb buttons are hovered
	//
	bool bBreak = false;
	m_pHoveredThumbButtonAsset = NULL;
	m_nHoveredThumbButtonIndex = 0;

	//
	// check if the cancel caching button is hovered, if yes, then skip other checks
	//
	if( m_bCachingInProgress && m_cancelCachingButtonRect.PtInRect( point ) )
	{
		m_bCancelCachingButtonHovered = true;
	}
	else
	{
		m_bCancelCachingButtonHovered = false;
	}

	if( !m_bDraggingAssetFromBrowser )
	{
		for( TThumbMiniButtonInstances::iterator iter = m_thumbMiniButtonInstances.begin(), iterEnd = m_thumbMiniButtonInstances.end(); iter != iterEnd && !bBreak; ++iter )
		{
			std::vector<CThumbMiniButtonInstance>& instances = iter->second;

			for( size_t i = 0, iCount = instances.size(); i < iCount; ++i )
			{
				if( instances[i].m_rect.PtInRect( point ) )
				{
					m_pHoveredThumbButtonAsset = iter->first;
					m_nHoveredThumbButtonIndex = i;
					bBreak = true;
					break;
				}
			}
		}
	}

	//
	// if we dropped an asset in the viewport, now we are moving it in the world to place it
	//
	if( m_bDraggingCreatedAssetInWorld
		&& m_pDropInViewport
		&& m_pDraggedAsset
		&& m_pDraggedAssetInstance )
	{
		Vec3 pos;

		CPoint vp = point;
		ClientToScreen( &vp );
		m_pDropInViewport->ScreenToClient( &vp );

		if( GetIEditor()->GetAxisConstrains() != AXIS_TERRAIN )
		{
			pos = m_pDropInViewport->MapViewToCP( vp );
		}
		else
		{
			bool bHitTerrain = false;

			pos = m_pDropInViewport->ViewToWorld( vp, &bHitTerrain );

			if( bHitTerrain )
			{
				pos.z = GetIEditor()->GetTerrainElevation( pos.x, pos.y );
			}
		}

		pos = m_pDropInViewport->SnapToGrid( pos );
		m_pDraggedAsset->MoveInstanceInViewport( m_pDraggedAssetInstance, pos.x, pos.y, pos.z );
		GetIEditor()->Get3DEngine()->Update();
		m_pDropInViewport->Update();
		GetIEditor()->GetViewManager()->UpdateViews();
	}

	//
	// update the dragging selection rectangle, but only if we're not dragging some asset out of the browser
	//
	if( m_bMouseLeftButtonDown && !m_bDraggingAssetFromBrowser )
	{
		m_oSelectionRect.right = point.x;
		m_oSelectionRect.bottom = point.y;
	}

	//
	// smooth panning the asset browser thumbs canvas
	//
	if( m_bMouseRightButtonDown )
	{
		m_smoothPanLastDelta = m_lastPanDragPt.y - point.y;
		m_nYOffset += m_smoothPanLastDelta;
		m_smoothPanLastDelta *= AssetViewer::kSmoothPanningDeltaBoost;
		m_lastPanDragPt = point;
		m_poEnsureVisible = NULL;
		UpdateScrollBar();
		UpdateVisibility();
	}

	if( m_smoothPanLastDelta )
	{
		// we must call the smooth panning here too, because when we move the mouse,
		// WM_TIMER messages do not get through, having lower priority
		// and the panning is not smooth, so we call this function in timer and also here, on mouse move
		// the function has builtin timing, does not rely on WM_TIMER tick
		UpdateSmoothPanning();
	}

	CachePendingNonThreadedAssets();
	Invalidate( FALSE );
}

void CAssetViewer::UpdateScrollBar()
{
	int nNewOffset = m_nYOffset;
	int nMaxScroll = m_nIdealClientHeight - m_oClientRect.Height();

	if( nMaxScroll < 0 )
	{
		nNewOffset = 0;
	}

	if( (int)m_nIdealClientHeight - nNewOffset < m_oClientRect.Height() )
	{
		nNewOffset = nMaxScroll;
	}

	if( nNewOffset < 0 )
	{
		nNewOffset = 0;
	}

	m_nYOffset = nNewOffset;
	SetScrollPos( SB_VERT, m_nYOffset );
	SetClientSize( m_nIdealClientWidth, m_nIdealClientHeight );
}

bool CAssetViewer::SortAssetsByIndex( IAssetDisplay* pA, IAssetDisplay* pB )
{
	assert( pA && pB );
	return pA->GetIndex() > pB->GetIndex();
}

void CAssetViewer::OnThumbButtonClickTags( IAssetDisplay* pAsset )
{
	CStringInputDialog dlg;
	CString tags;

	pAsset->GetAssetFieldValue( "tags", &tags );

	dlg.SetText( tags );
	dlg.SetTitle( "Asset tags" );

	if( dlg.DoModal() == IDOK )
	{
		tags = dlg.GetResultingText();
		//pAsset->SetAssetFieldValue( "tags", &tags );
		//pAsset->GetOwnerDisplayDatabase()->OnMetaDataChange( pAsset );
	}
}

void CAssetViewer::OnThumbButtonClickFavourite( IAssetDisplay* pAsset )
{
	pAsset->SetFlag( IAssetDisplay::eAssetFlags_Favourite, !pAsset->IsFlagSet( IAssetDisplay::eAssetFlags_Favourite ) );
	pAsset->GetOwnerDisplayDatabase()->OnMetaDataChange( pAsset );
}

void CAssetViewer::OnLButtonUp( UINT nFlags, CPoint point )
{
	ReleaseCapture();
	m_bDragging = false;
	LockAssetItemsArray();
	m_lastPanDragPt = point;

	if( m_bDraggingAssetFromBrowser && m_poClickedAsset )
	{
		m_bDraggingAssetFromBrowser = false;
		CPoint vp = point;
		ClientToScreen( &vp );
		m_pDropInViewport = GetIEditor()->GetViewManager()->GetViewportAtPoint( vp );

		if( m_pDropInViewport )
		{
			Vec3 pos;

			m_pDropInViewport->ScreenToClient( &vp );

			if( GetIEditor()->GetAxisConstrains() != AXIS_TERRAIN )
			{
				pos = m_pDropInViewport->MapViewToCP( vp );
			}
			else
			{
				bool bHitTerrain = false;
				
				pos = m_pDropInViewport->ViewToWorld( vp, &bHitTerrain );
				
				if( bHitTerrain )
				{
					pos.z = GetIEditor()->GetTerrainElevation( pos.x, pos.y );//TODO: why 1m from ground? + 1.0f;
				}
			}

			pos = m_pDropInViewport->SnapToGrid( pos );

			m_pDraggedAssetInstance = m_poClickedAsset->CreateInstanceInViewport( pos.x, pos.y, pos.z );
			m_pDraggedAsset = m_poClickedAsset;
			m_bDraggingCreatedAssetInWorld = true;
			
			if( m_pDraggedAsset->IsFlagSet( IAssetDisplay::eAssetFlags_CanBeMovedAfterDroppedIntoViewport ) )
			{
				// we set capture yet again, beginning to drag/move in the world the newly dropped asset instance
				SetCapture();
			}
			else
			{
				// end of dragging, clear up stuff
				m_pDraggedAssetInstance = NULL;
				m_pDraggedAsset = NULL;
				m_bDraggingCreatedAssetInWorld = false;
				m_bDraggingAssetFromBrowser = false;
				m_pDropInViewport = NULL;
			}
		}
	}
	else
	if( !m_bCancelCachingButtonHovered )
	{
		if( m_pHoveredThumbButtonAsset
			&& m_thumbMiniButtonInstances[m_pHoveredThumbButtonAsset][m_nHoveredThumbButtonIndex].m_pMiniButton->m_pfnOnClick )
		{
			m_thumbMiniButtonInstances[m_pHoveredThumbButtonAsset][m_nHoveredThumbButtonIndex].m_pMiniButton->m_pfnOnClick(
																																																m_pHoveredThumbButtonAsset );
		}

		if( m_bMouseLeftButtonDown )
		{
			m_oSelectionRect.right = point.x;
			m_oSelectionRect.bottom = point.y;

			if( m_oSelectionRect.left > m_oSelectionRect.right )
			{
				std::swap( m_oSelectionRect.left, m_oSelectionRect.right );
			}

			if( m_oSelectionRect.top > m_oSelectionRect.bottom )
			{
				std::swap( m_oSelectionRect.top, m_oSelectionRect.bottom );
			}

			// For adjust the scrolling position to the hit test.
			// For drawing things to the client area we should
			// subtract the scroll position instead.
			CRect stCurrentRect = m_oSelectionRect;

			stCurrentRect.top += m_nYOffset;
			stCurrentRect.bottom += m_nYOffset;
			m_bDragging = false;
			m_bMouseLeftButtonDown = false;

			// For adjust the scrolling position to the hit test.
			// For drawing things to the client area we should
			// subtract the scroll position instead.
			POINT stHitTestPoint( point );
			stHitTestPoint.y += m_nYOffset;

			CString		strSelectedFilename;
			CString		strDirectory;
			bool			bSelect = false;
			__int64		nTotalSelectionSize = 0;
			bool			bAddSelection = CheckVirtualKey( VK_LCONTROL );
			bool			bUnselect =	CheckVirtualKey( VK_LMENU );
			bool			bSelectedRange =	CheckVirtualKey( VK_LSHIFT ) | CheckVirtualKey( VK_RSHIFT );
			bool			bStartSelection = ( !bAddSelection && !bUnselect );
			bool			bHitItem = false;
			bool			bClickSelection =
									(stCurrentRect.Width() <= AssetViewer::kMinimumSelectionDraggingOffset
									&& stCurrentRect.Height() <= AssetViewer::kMinimumSelectionDraggingOffset);

			if( m_poClickedAsset && !bClickSelection )
			{
				if( !bAddSelection && !bSelectedRange )
				{
					UnlockAssetItemsArray();
					DeselectAll();
					LockAssetItemsArray();
					m_poClickedAsset->SetFlag( IAssetDisplay::eAssetFlags_Selected, true );
					m_cSelectedAssets.push_back( m_poClickedAsset );
				}
			}
			else
			if( m_poClickedAsset && bSelectedRange )
			{
				if( bClickSelection && !m_cSelectedAssets.empty() )
				{
					IAssetDisplay* pLastSelectedAsset = m_cSelectedAssets.back();

					int direction = pLastSelectedAsset->GetIndex() <= m_poClickedAsset->GetIndex() ? 1 : -1;

					m_cSelectedAssets.resize( 0 );
					
					// unselect all, SHIFT selection is exclusive
					for( size_t nCurrentAsset = 0, nTotalAssets = m_cAssetItems.size(); nCurrentAsset < nTotalAssets; ++nCurrentAsset )
					{
						IAssetDisplay* const cpoCurrentItem = m_cAssetItems[nCurrentAsset];

						cpoCurrentItem->SetFlag( IAssetDisplay::eAssetFlags_Selected, false );
					}			

					// get last asset in selection
					for(int iFrom = pLastSelectedAsset->GetIndex() + direction,
							iTo = m_poClickedAsset->GetIndex();
							( direction > 0 ? iFrom <= iTo : iFrom >= iTo ) ;
							iFrom += direction )
					{
						IAssetDisplay* const cpoCurrentItem = m_cAssetItems[iFrom];

						if( !cpoCurrentItem->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
						{
							continue;
						}

						cpoCurrentItem->SetFlag( IAssetDisplay::eAssetFlags_Selected, true );
						m_cSelectedAssets.push_back( cpoCurrentItem );
					}

					pLastSelectedAsset->SetFlag( IAssetDisplay::eAssetFlags_Selected, true );
					m_cSelectedAssets.push_back( pLastSelectedAsset );
				}
			}
			else
			{
				m_cSelectedAssets.resize( 0 );

				for( size_t nCurrentAsset = 0, nTotalAssets = m_cAssetItems.size(); nCurrentAsset < nTotalAssets; ++nCurrentAsset )
				{
					IAssetDisplay* const cpoCurrentItem = m_cAssetItems[nCurrentAsset];

					if( !cpoCurrentItem->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
					{
						continue;
					}

					if( bClickSelection )
					{
						bHitItem = cpoCurrentItem->HitTest( stHitTestPoint.x, stHitTestPoint.y );
					}
					else
					{
						bHitItem = cpoCurrentItem->HitTest( stCurrentRect );
					}

					if( bHitItem )
					{
						// When you're clicking over an unselected item, even in unselect mode you will
						// want to add it to selection.
						if( bClickSelection )
						{
							// When you're clicking over an unselected item, even in unselect mode you will
							// want to add it to selection. (actually this inverses selection behavior)
							bSelect = !cpoCurrentItem->IsFlagSet( IAssetDisplay::eAssetFlags_Selected );
							cpoCurrentItem->SetFlag( IAssetDisplay::eAssetFlags_Selected, bSelect );

							if( bSelect )
							{
								m_cSelectedAssets.push_back( cpoCurrentItem );
							}
						}
						else
						{
							// We have to add those which hit the selection unless we are unselecting items
							// so if unselect is false, it will select items, otherwise it will unselect them
							bSelect = !bUnselect;
							cpoCurrentItem->SetFlag( IAssetDisplay::eAssetFlags_Selected, bSelect );

							if( bSelect )
							{
								m_cSelectedAssets.push_back( cpoCurrentItem );
							}
						}
					}
					else
					{
						// If we are starting a new selection, the item wasn't hit in the selection, 
						// we must unselect it.
						if( bStartSelection )
						{
							cpoCurrentItem->SetFlag( IAssetDisplay::eAssetFlags_Selected, false );
						}
						else
						{
							if( cpoCurrentItem->IsFlagSet( IAssetDisplay::eAssetFlags_Selected ) )
							{
								m_cSelectedAssets.push_back( cpoCurrentItem );
							}
						}
					}
				}
			}

			std::sort( m_cSelectedAssets.begin(), m_cSelectedAssets.end(), SortAssetsByIndex );
		}
	}

	UnlockAssetItemsArray();
	CALL_OBSERVERS_METHOD( OnChangeStatusBarInfo( m_cSelectedAssets.size(), m_cAssetItems.size(), m_nTotalAssets ) );
	CALL_OBSERVERS_METHOD( OnSelectionChanged() );
	CALL_OBSERVERS_METHOD( OnChangedPreviewedAsset( m_poClickedAsset ) );
	Invalidate( FALSE );
}

void CAssetViewer::OnLButtonDblClk( UINT nFlags, CPoint point )
{
	CALL_OBSERVERS_METHOD( OnChangeStatusBarInfo( m_cSelectedAssets.size(), m_cAssetItems.size(), m_nTotalAssets ) );
	CALL_OBSERVERS_METHOD( OnSelectionChanged() );

	//
	// if we have a double clicked asset thumb, do notify it
	//
	if( !m_cSelectedAssets.empty() )
	{
		m_cSelectedAssets[0]->OnThumbDblClick( point, nFlags );
	}
}

void CAssetViewer::OnRButtonDown(UINT nFlags, CPoint point)
{
	m_bMouseRightButtonDown = true;
	m_lastPanDragPt = m_lastRightClickPt = point;
	m_smoothPanLeftAmount = 0;
	m_smoothPanLastDelta = 0;
	CheckClickedThumb( nFlags, point );
	SetCapture();
	SetFocus();
	SetCursor( LoadCursor( AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDC_CURSOR_HAND_DRAG) ) );

	__super::OnRButtonDown(nFlags, point);
}

void CAssetViewer::OnRButtonUp(UINT nFlags, CPoint point)
{
	SetCursor( LoadCursor( NULL, IDC_ARROW ) );
	ReleaseCapture();
	m_bMouseRightButtonDown = false;
	m_smoothPanLeftAmount = m_smoothPanLastDelta;

	LockAssetItemsArray();
	
	// if we clicked an asset and we didnt moved the mouse (too much), then show the context menu
	if( m_poClickedAsset && 
			( m_lastRightClickPt.x - point.x ) <= AssetViewer::kMinimumStartPanningOffset &&
			( m_lastRightClickPt.y - point.y ) <= AssetViewer::kMinimumStartPanningOffset )
	{
		int saveReportItemId;
	
		CFileUtil::ExtraMenuItems extraMenuItems;
		saveReportItemId = extraMenuItems.AddItem( "Save Report" );

		CFileUtil::PopupMenu(	m_poClickedAsset->GetFilename(), m_poClickedAsset->GetRelativePath(), this,
													0, &extraMenuItems );

		if( extraMenuItems.selectedIndexIfAny == saveReportItemId )
		{
			CString filePath = m_poClickedAsset->GetFilename();

			if( CFileUtil::SelectSaveFile(	"Report Files (*.txt, *.bmp)|*.txt;*.bmp|All files|*.*||",
																			"txt", "", filePath ) )
			{
				CString filePathTXT = Path::ReplaceExtension( filePath, "txt" );
				m_poClickedAsset->SaveReportText( filePathTXT.GetBuffer() );

				CString filePathBMP = Path::ReplaceExtension( filePath, "bmp" );
				m_poClickedAsset->SaveReportImage( filePathBMP.GetBuffer() );
			}
		}
	}

	UnlockAssetItemsArray();

	// if we have smooth panning movement, start the smooth panning timer operation
	if( fabs( m_smoothPanLeftAmount ) )
	{
		m_smoothPanningLastTickCount = GetTickCount();
		SetTimer( ID_ASSET_VIEWER_TIMER_SMOOTH_PANNING, AssetViewer::kSmoothPanningTimerDelay, NULL );
	}

	SetFocus();

	__super::OnRButtonUp(nFlags, point);
}

void CAssetViewer::OnMButtonDown(UINT nFlags, CPoint point)
{
	m_bMouseMiddleButtonDown = true;
	m_lastPanDragPt = point;
	m_smoothPanLeftAmount = 0;
	m_smoothPanLastDelta = 0;
	SetCapture();
	SetFocus();
	CheckClickedThumb( nFlags, point );

	__super::OnMButtonDown(nFlags, point);
}

void CAssetViewer::OnMButtonUp(UINT nFlags, CPoint point)
{
	ReleaseCapture();
	m_bMouseMiddleButtonDown = false;
	LockAssetItemsArray();
	m_poClickedAsset = NULL;
	UnlockAssetItemsArray();

	__super::OnMButtonUp(nFlags, point);
}

void CAssetViewer::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	__super::OnVScroll( nSBCode, nPos, pScrollBar );
	m_poEnsureVisible = NULL;
	m_nYOffset = GetScrollPos( SB_VERT );
	m_smoothPanLeftAmount = 0;
	m_smoothPanLastDelta = 0;
	KillTimer( ID_ASSET_VIEWER_TIMER_SMOOTH_PANNING );
	UpdateScrollBar();
	UpdateVisibility();
	Invalidate( FALSE );
}

BOOL CAssetViewer::OnMouseWheel( UINT nFlags, short zDelta, CPoint pt )
{
	GetClientRect( m_oClientRect );
	GetCursorPos( &pt );
	ScreenToClient( &pt );

	if( m_oClientRect.PtInRect( pt ) )
	{
		m_nYOffset -= zDelta;
		m_poEnsureVisible = NULL;
		UpdateScrollBar();
		UpdateVisibility();
		Invalidate( FALSE );
	}

	return TRUE;
}

void CAssetViewer::OnTimer( UINT_PTR nIDEvent )
{
	if( nIDEvent == ID_ASSET_VIEWER_TIMER_SMOOTH_PANNING )
	{
		UpdateSmoothPanning();
		Invalidate( FALSE );
	}
	else
	if( nIDEvent == ID_ASSET_VIEWER_TIMER_CACHE_ASSET )
	{
		CachePendingNonThreadedAssets();
	}
	else
	if( nIDEvent == ID_ASSET_VIEWER_TIMER_WARNING_BLINK_TOGGLER )
	{
		m_bWarningBlinkToggler = !m_bWarningBlinkToggler;
		Invalidate( FALSE );
	}
	else
	if( nIDEvent == ID_ASSET_VIEWER_TIMER_THUMB_FOCUS_ANIMATION )
	{
		if( !m_poEnsureVisible )
		{
			m_thumbFocusAnimationTime = 1;
			KillTimer( ID_ASSET_VIEWER_TIMER_THUMB_FOCUS_ANIMATION );
			Invalidate( FALSE );
			return;
		}

		CRect rc;
		
		m_poEnsureVisible->GetDrawingRectangle( rc );
		rc.OffsetRect( 0, -m_nYOffset );

		m_thumbFocusAnimationRect.left = m_oClientRect.left + m_thumbFocusAnimationTime * ( rc.left - m_oClientRect.left );
		m_thumbFocusAnimationRect.right = m_oClientRect.right + m_thumbFocusAnimationTime * ( rc.right - m_oClientRect.right );
		m_thumbFocusAnimationRect.top = m_oClientRect.top + m_thumbFocusAnimationTime * ( rc.top - m_oClientRect.top );
		m_thumbFocusAnimationRect.bottom = m_oClientRect.bottom + m_thumbFocusAnimationTime * ( rc.bottom - m_oClientRect.bottom );
		m_thumbFocusAnimationTime += 0.1f;

		Invalidate( FALSE );
		
		if( m_thumbFocusAnimationTime >= 1 )
		{
			m_thumbFocusAnimationTime = 1;
			KillTimer( ID_ASSET_VIEWER_TIMER_THUMB_FOCUS_ANIMATION );
		}
	}
}

void CAssetViewer::CachePendingNonThreadedAssets()
{
	// CryPak and other classes are showing non full app modal dialog boxes, and the WM_TIMER is still sent, so check if we're not already inside
	static volatile bool bWorking = false;

	if( bWorking )
		return;

	bWorking = true;

	if( m_cPendingCacheAssets.empty() )
	{
		KillTimer( ID_ASSET_VIEWER_TIMER_CACHE_ASSET );
		bWorking = false;
		return;
	}

	IAssetDisplay* pAsset = m_cPendingCacheAssets[0];

	LockAssetItemsArray();
	
	if( pAsset )
	{
		// no need to cache it if not visible anymore
		if( m_cAssetDrawingCache.end() == std::find( m_cAssetDrawingCache.begin(), m_cAssetDrawingCache.end(), pAsset ) )
		{
			m_cPendingCacheAssets.erase( m_cPendingCacheAssets.begin() );
			bWorking = false;
			UnlockAssetItemsArray();
			return;
		}

		if( !pAsset->IsFlagSet( IAssetDisplay::eAssetFlags_Cached ) )
		{
			if( pAsset->Cache() )
			{
				m_cPendingCacheAssets.erase( m_cPendingCacheAssets.begin() );
				Invalidate( FALSE );
			}
		}
	}

	UnlockAssetItemsArray();

	bWorking = false;
}

void CAssetViewer::UpdateSmoothPanning()
{
	if( 0 == m_smoothPanLeftAmount )
	{
		return;
	}

	UINT crtTick = GetTickCount();
	UINT delta = crtTick - m_smoothPanningLastTickCount;
	m_smoothPanningLastTickCount = crtTick;
	float fDeltaTime = (float)delta / 1000.0f;
	static float sfTimerAmount = 0;
	sfTimerAmount += fDeltaTime;
	
	if( sfTimerAmount < AssetViewer::kSmoothPanning_Speed )
	{
		return;
	}

	sfTimerAmount = 0;
	m_smoothPanLeftAmount *= AssetViewer::kSmoothPanning_SlowdownMultiplier;
	m_nYOffset += m_smoothPanLeftAmount;

	// if smaller than 1 pixel, dont bother, end smooth panning operation
	if( fabs( m_smoothPanLeftAmount ) < 1.0f )
	{
		m_smoothPanLeftAmount = 0;
		m_smoothPanLastDelta = 0;
		KillTimer( ID_ASSET_VIEWER_TIMER_SMOOTH_PANNING );
	}

	m_poEnsureVisible = NULL;
	UpdateScrollBar();
	UpdateVisibility();
}

void CAssetViewer::RecalculateLayout()
{
	int		nCurrentTop = 0, nCurrentLeft = 0;
	UINT	thumbsPerRow = 0;
	float	horizMargin = 0;
	UINT	rowItemCount = 0;
	CRect	rstElementRect;

	// magic! compute number of thumbs per one row of asset thumbs, and the needed margin between them
	CGdiCanvas::ComputeThumbsLayoutInfo( m_oClientRect.Width(), m_nAssetThumbSize, m_nItemHorizontalSpacing, m_cAssetItems.size(), thumbsPerRow, horizMargin );

	m_nThumbsPerRow = thumbsPerRow;
	nCurrentTop = m_nItemVerticalSpacing;
	nCurrentLeft = horizMargin;

	for( size_t nCurrentItem = 0, nTotalItems = m_cAssetItems.size(); nCurrentItem < nTotalItems; ++nCurrentItem )
	{
		IAssetDisplay* const cpoCurrentAssetItem = m_cAssetItems[nCurrentItem];

		// Invisible items, due to filtering or any other reason
		// are not considered in the layout calculation...
		if( !cpoCurrentAssetItem->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
		{
			continue;
		}

		rstElementRect.left = nCurrentLeft;
		rstElementRect.right = rstElementRect.left + m_nAssetThumbSize;
		rstElementRect.top = nCurrentTop;
		rstElementRect.bottom = rstElementRect.top + m_nAssetThumbSize;

		cpoCurrentAssetItem->SetDrawingRectangle( rstElementRect );

		nCurrentLeft += m_nAssetThumbSize + horizMargin * 2;
		++rowItemCount;

		// begin a new row of thumbs
		if( rowItemCount >= thumbsPerRow )
		{
			rowItemCount = 0;
			nCurrentLeft = horizMargin;
			nCurrentTop += m_nItemVerticalSpacing * 2 + m_nAssetThumbSize;
		}
	}

	m_nIdealClientHeight = nCurrentTop + m_nAssetThumbSize + 2*m_nItemVerticalSpacing;

	if( m_nIdealClientHeight > m_oClientRect.Height() )
	{
		m_nIdealClientWidth = m_oClientRect.Width() - GetSystemMetrics( SM_CXVSCROLL );
	}
	else
	{
		m_nIdealClientWidth = m_oClientRect.Width();
	}
}

void CAssetViewer::UpdateVisibility()
{
	CRect oCurrentRect;
	CRect oIntersection;

	LockAssetItemsArray();

	// First we check all bitmaps which were previously visible and which are not anymore
	// and then we uncache them. The ones still visible will keep cached.
	for( TAssetItems::iterator iter = m_cAssetItems.begin(), iterEnd = m_cAssetItems.end(); iter != iterEnd; ++iter )
	{
		IAssetDisplay* const cpoDatabaseItem = *iter;

		// No need to cache bitmaps that should not be displayed for one reason or another...
		// specially because we have no guarantee that their positions will be valid...
		// so it is a cached item, we must remove it from the cache.
		if( !cpoDatabaseItem->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
		{
			// do not uncache the current clicked asset
			if( m_poClickedAsset != cpoDatabaseItem )
			{
				cpoDatabaseItem->UnCache();
			}

			continue;
		}

		cpoDatabaseItem->GetDrawingRectangle( oCurrentRect );
		oCurrentRect.top -= m_nYOffset;
		oCurrentRect.bottom -= m_nYOffset;

		// We don't need to uncache Assets which are still visible...
		if( oIntersection.IntersectRect( &oCurrentRect, &m_oClientRect ) )
		{
			continue;
		}

		// do not uncache the current clicked asset
		if( m_poClickedAsset != cpoDatabaseItem )
		{
			cpoDatabaseItem->UnCache();
		}
	}

	m_cAssetDrawingCache.resize( 0 );

	for( size_t nCurrentItem = 0, nTotalItems = m_cAssetItems.size(); nCurrentItem < nTotalItems; ++nCurrentItem )
	{
		IAssetDisplay* const cpoDatabaseItem = m_cAssetItems[nCurrentItem];

		// No need to cache bitmaps that should not be displayed for one reason or another...
		// specially because we have no guarantee that their positions will be valid...
		if( !cpoDatabaseItem->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
		{
			continue;
		}

		cpoDatabaseItem->GetDrawingRectangle( oCurrentRect );

		// We may need to have 2 copies of the rectangles, one the drawing and one the
		// layout rectangle...
		oCurrentRect.top -= m_nYOffset;
		oCurrentRect.bottom -= m_nYOffset;

		// If the item is entirely outside of the client rect, no need to cache a bitmap for it.
		if( oCurrentRect.bottom < 0 )
		{
			continue;
		}

		// If the item is entirely below the client rect, as Y coordinates grow monotonically,
		// no need to cache anything else.
		if( oCurrentRect.top > m_oClientRect.bottom )
		{
			break;
		}

		// Added to use the same criteria as in the remove list.
		if( !oIntersection.IntersectRect( m_oClientRect, oCurrentRect ) )
		{
			continue;
		}

		m_cAssetDrawingCache.push_back( cpoDatabaseItem );
	}

	if( m_nThumbsPerRow )
	{
		m_nVisibleThumbRows = m_cAssetDrawingCache.size() / m_nThumbsPerRow;
	}
	else
	{
		m_nVisibleThumbRows = 0;
	}

	UnlockAssetItemsArray();
}

void CAssetViewer::Draw()
{
	CString		strFilename;
	CRect			stElementRect;
	CRect			stIntersection;
	CDC&			dc = m_canvas.GetDC();

	dc.SetBrushOrg( 0, -m_nYOffset );
	dc.FillRect( &m_oClientRect, &m_backBrush );
	dc.SetBrushOrg( 0, 0 );

	if( !CAssetMetaDataFileDB::IsLoadAndUpdateDone() )
	{
		CFont fntBig;
		fntBig.CreatePointFont( 110, "Verdana Bold" );
		dc.SelectObject( fntBig );
		dc.SetTextColor( RGB( 255, 255, 255 ) );
		CString str;

		// advance to the end of the dots string, and subtract the number (0,1,2,3) to get the current address of the beginning of the dots
		// we're using GetTickCount to get timed animation, the division is for slowdown purposes
		const char* pAnimatedDots = ( "..." + 3 ) - ( GetTickCount() / 500 /*milliseconds*/ ) % 4;
		str.Format( "%sRefreshing%s", pAnimatedDots, pAnimatedDots );

		dc.DrawText( str, &m_oClientRect, DT_SINGLELINE|DT_VCENTER|DT_CENTER|DT_WORDBREAK|DT_WORD_ELLIPSIS );

		fntBig.DeleteObject();
		m_canvas.BlitTo( m_hWnd );
		return;
	}

	m_thumbMiniButtonInstances.clear();

	for( size_t nCurrentAsset = 0, nTotalAssets = m_cAssetDrawingCache.size(); nCurrentAsset < nTotalAssets; ++nCurrentAsset )
	{
		IAssetDisplay* const cpoAssetDatabaseItem = m_cAssetDrawingCache[nCurrentAsset];

		cpoAssetDatabaseItem->GetDrawingRectangle( stElementRect );
		stElementRect.top -= m_nYOffset;
		stElementRect.bottom -= m_nYOffset;

		// If the rectangle is outside, skip it
		if( !stIntersection.IntersectRect( &stElementRect, &m_oClientRect ) )
		{
			continue;
		}

		DrawAsset( cpoAssetDatabaseItem, stElementRect );
	}

	if( m_bDragging && !m_poClickedAsset )
	{
		CPen pen;
		pen.CreatePen( PS_SOLID, 2, AssetViewer::kSelectionDragLineColor );
		dc.SelectObject( pen );
		dc.SelectObject( GetStockObject( NULL_BRUSH ) );
		dc.Rectangle( m_oSelectionRect );
		pen.DeleteObject();
	}

	CheckHoveredThumb();
	DrawThumbTooltip( dc );
	DrawFocusThumbAnimationRect( dc );
	DrawThumbMiniButtonTooltip( dc );
	DrawCachingInfoProgressBar( dc );
	
	m_canvas.BlitTo( m_hWnd );
}

void CAssetViewer::DrawAsset( IAssetDisplay* poItem, CRect& roUpdateRect )
{
	CString		strOutputText;
	bool			bSelected = poItem->IsFlagSet( IAssetDisplay::eAssetFlags_Selected ), bHovered = ( poItem == m_poHoveredAsset );
	CString		strTempText, strAssetThumbOneLineInfo;
	CString		strLabel, strInfo, strAssetTags;
	CString		strTmp;
	bool			bTagged = false, bFav = false;

	CDC& dc = m_canvas.GetDC();
	CRect rcShadow = roUpdateRect;
	CRect rcBorder = roUpdateRect;
	CRect rcLabel = roUpdateRect;

	rcBorder.InflateRect( 5, 5 );
	rcShadow.InflateRect( 15, 15 );

	poItem->GetAssetFieldValue( "filename", &strLabel );
	
	// kill extension, irelevant
	strLabel.Replace( CString( "." ) + poItem->GetFileExtension(), "" );
	poItem->GetAssetFieldValue( "thumbOneLineText", &strAssetThumbOneLineInfo );
	poItem->GetAssetFieldValue( "tags", &strAssetTags );
	poItem->GetAssetFieldValue( "favourite", &bFav );

	bTagged = !strAssetTags.IsEmpty();
	strOutputText = strLabel;
	dc.SelectObject( m_fontLabel );

	rcLabel.top = rcLabel.bottom + 10;
	rcLabel.bottom = rcLabel.top + m_nItemVerticalSpacing;
	m_canvas.BreakTextByChars( strOutputText, strLabel, rcLabel );
	// no text draw, just compute rect
	dc.DrawText( strLabel, &rcLabel, DT_CENTER | DT_CALCRECT );

	const int kBorderRadius = 0;

	//
	// draw the label
	//
	rcLabel.left = roUpdateRect.left;
	rcLabel.right = roUpdateRect.right;
	dc.SetTextColor( AssetViewer::kLabelTextColor );
	dc.DrawText( strLabel, &rcLabel, DT_CENTER );

	//
	// draw one line info text for thumb
	//
	rcLabel.top = rcLabel.bottom + 10;
	rcLabel.bottom = rcLabel.top + 15;
	dc.SetTextColor( AssetViewer::kOneLineInfoTextColor );
	dc.SelectObject( m_fontOneLinerInfo );
	dc.DrawText( strAssetThumbOneLineInfo, &rcLabel, DT_CENTER | DT_NOCLIP );

	//
	// draw the thumb container
	//
	CPen penContainer;
	CBrush brushContainer;

	penContainer.CreatePen( PS_SOLID, 1, bHovered ? AssetViewer::kThumbContainerHoverLineColor : AssetViewer::kThumbContainerLineColor );
	brushContainer.CreateSolidBrush(AssetViewer::kThumbContainerBackColor);
	dc.SelectObject( brushContainer );
	dc.SelectObject( penContainer );
	
	if( bSelected )
	{
		rcBorder.InflateRect( 4, 4 );
	}

	dc.RoundRect( &rcBorder, CPoint( kBorderRadius, kBorderRadius ) );
	penContainer.DeleteObject();

	//
	// draw selection rect
	//
	if( poItem->IsFlagSet( IAssetDisplay::eAssetFlags_Selected ) )
	{
		CRect stSelectionRect( roUpdateRect );

		stSelectionRect.InflateRect( 5, 5 );
		++stSelectionRect.left;
		++stSelectionRect.top;

		CPen pen( PS_SOLID, 2, RGB( 255, 255, 0 ) );
		dc.SelectObject( pen );
		dc.SelectObject( GetStockObject( HOLLOW_BRUSH ) );
		dc.RoundRect( &stSelectionRect, CPoint(kBorderRadius, kBorderRadius) );
		pen.DeleteObject();
	}

	// cache items that were not cached in the caching thread, due to no eAssetFlags_ThreadCachingSupported flag
	if( !poItem->IsFlagSet( IAssetDisplay::eAssetFlags_Cached ) &&
			!poItem->IsFlagSet( IAssetDisplay::eAssetFlags_ThreadCachingSupported ) )
	{
		EnqueueAssetForCaching( poItem );
	}

	// this asset needs to cache its thumbnail
	if( poItem->IsFlagSet( IAssetDisplay::eAssetFlags_Cached ) )
	{
		if(	!poItem->IsFlagSet( IAssetDisplay::eAssetFlags_ThumbnailCached ) &&
			!IsThumbViewportInteraction() )
		{
			CRect rc;
			m_wndAssetThumbRender.GetClientRect( &rc );
			m_wndAssetThumbRender.SetWindowPos( 0, 0, 0, m_nAssetThumbSize, m_nAssetThumbSize, 0 );
			poItem->Render( m_wndAssetThumbRender.GetSafeHwnd(), &rc, true /*cache thumbnail*/ );
		}
	}

	DrawThumbnail( dc, poItem, roUpdateRect );
	DrawThumbMiniButtons( poItem, rcBorder );
}

void CAssetViewer::DrawFocusThumbAnimationRect( CDC& dc )
{
	//
	// draw thumb focus animation rect
	//
	if( m_thumbFocusAnimationTime != 1 )
	{
		CPen pen;
		pen.CreatePen( PS_SOLID, 2, RGB( 255, 255, 255 ) );
		dc.SelectObject( (HGDIOBJ)GetStockObject( HOLLOW_BRUSH ) );
		dc.SelectObject( pen );
		dc.Rectangle( &m_thumbFocusAnimationRect );
		pen.DeleteObject();
	}
}

void CAssetViewer::DrawThumbTooltip( CDC& dc )
{
	if( m_poHoveredAsset )
	{
		CPen		penTip;
		CBrush	brushTip;
		CRect		rcTip, rcTitle, rcInfo, rcThumb;
		CString	strFilename, strAssetThumbInfo;
		CString	strAssetThumbInfoFinal;
		CSize		titleSize;
		CPoint	point;

		GetCursorPos( &point );
		ScreenToClient( &point );

		m_poHoveredAsset->GetAssetFieldValue( "filename", &strFilename );
		m_poHoveredAsset->GetAssetFieldValue( "thumbToolTipText", &strAssetThumbInfo );
		m_poHoveredAsset->GetDrawingRectangle( rcThumb );

		rcTip.left = point.x;
		rcTip.top = point.y + AssetViewer::kTooltip_Margin;
		rcTip.right = rcTip.left + AssetViewer::kTooltip_MinWidth;
		rcTip.bottom = rcTip.top + AssetViewer::kTooltip_MinHeight;

		dc.SelectObject( m_fontInfoTitle );
		titleSize = dc.GetTextExtent( strFilename );

		// choose the larger one
		rcTip.right = rcTip.left + MAX( AssetViewer::kTooltip_MinWidth, titleSize.cx ) + AssetViewer::kTooltip_Margin;
		rcInfo = rcTip;

		// compute text bounds, no draw
		dc.SelectObject( m_fontInfoText );
		strAssetThumbInfoFinal = strAssetThumbInfo;
		dc.DrawText( strAssetThumbInfoFinal, &rcInfo, DT_CALCRECT|DT_WORDBREAK );

		rcTip.bottom = rcTip.top + titleSize.cy + AssetViewer::kTooltip_Margin + rcInfo.Height() + AssetViewer::kTooltip_TitleContentSpacing;
		rcTip.right = max( rcTip.left + rcInfo.Width() + AssetViewer::kTooltip_Margin, rcTip.right );

		//
		// relocate tip box if is overlapping the thumb
		//

		rcThumb.OffsetRect( 0, -m_nYOffset );

		int deltaX = rcTip.left - rcThumb.right;
		int deltaY = rcTip.top - rcThumb.bottom;

		if( deltaX < 0 && deltaY < 0 )
		{
			if( abs( deltaX ) >= abs( deltaY ) )
			{
				rcTip.OffsetRect( 0, -deltaY );
			}
			else
			{
				rcTip.OffsetRect( -deltaX, 0 );
			}
		}

		//
		// relocate tip box, if out of bounds
		//
		if( rcTip.right > m_oClientRect.Width() )
		{
			int delta = rcTip.right - m_oClientRect.Width();
			rcTip.left -= delta;
			rcTip.right -= delta;
		}

		if( rcTip.bottom > m_oClientRect.Height() )
		{
			int delta = rcTip.bottom - m_oClientRect.Height();
			rcTip.top -= delta;
			rcTip.bottom -= delta;
		}

		//
		// draw tip box shadow
		//
		CRect rcShadow = rcTip;
		rcShadow.InflateRect( 12, 5 );
		m_canvas.BitBltWithAlpha( m_thumbShadowBmp.GetDC(), rcShadow.left, rcShadow.top, rcShadow.Width(), rcShadow.Height() );

		//
		// draw tip box round rectangle
		//
		penTip.CreatePen( PS_SOLID, AssetViewer::kTooltip_BorderWidth, AssetViewer::kTooltip_BorderColor );
		dc.SelectObject( penTip );
		dc.SelectObject( GetStockObject( HOLLOW_BRUSH ) );
		dc.RoundRect( &rcTip, CPoint( AssetViewer::kTooltip_BorderCornerSize, AssetViewer::kTooltip_BorderCornerSize ) );

		//
		// draw small info icon
		//
		HICON hIconInfo = LoadIcon( NULL, IDI_INFORMATION );
		DrawIconEx( dc.GetSafeHdc(), rcTip.right - 32,  rcTip.bottom - 32, hIconInfo, 16, 16, 0, 0, DI_NORMAL );

		//
		// draw filename
		//
		dc.SelectObject( m_fontInfoTitle );
		dc.SetTextColor( AssetViewer::kTooltip_FilenameShadowColor );
		dc.TextOut( rcTip.left + 12, rcTip.top + 12, strFilename );
		dc.SetTextColor( AssetViewer::kTooltip_FilenameColor );
		dc.TextOut( rcTip.left + 10, rcTip.top + 10, strFilename );

		//
		// draw info text lines
		//
		CRect rcTextInfo = rcTip;
		rcTextInfo.left += 10;
		rcTextInfo.right -= 10;
		rcTextInfo.top = rcTip.top + titleSize.cy + 15;
		rcTextInfo.bottom -= 10;
		dc.SetTextColor( AssetViewer::kInfoTextColor );
		dc.SelectObject( m_fontInfoText );
		dc.DrawText( strAssetThumbInfoFinal, &rcTextInfo, DT_WORDBREAK );

		penTip.DeleteObject();
	}
}

void CAssetViewer::DrawThumbMiniButtonTooltip( CDC& dc )
{
	//
	// draw the tooltip for the mini thumb button currently hovered, if any
	//
	if( !m_thumbMiniButtonInstances.empty() && m_pHoveredThumbButtonAsset != NULL )
	{
		if( !m_thumbMiniButtonInstances[m_pHoveredThumbButtonAsset].empty()
			&& m_nHoveredThumbButtonIndex >= 0
			&& m_nHoveredThumbButtonIndex < m_thumbMiniButtonInstances[m_pHoveredThumbButtonAsset].size() )
		{
			CThumbMiniButtonInstance& btnInst = m_thumbMiniButtonInstances[m_pHoveredThumbButtonAsset][m_nHoveredThumbButtonIndex];
			CRect rcTip;
			CRect rcBtn = btnInst.m_rect;
			CFont fntTip;
			CPen pen;
			CSize textSize;

			fntTip.CreatePointFont( 65, "Arial" );
			dc.SelectObject( fntTip );
			dc.SetTextColor(AssetViewer::kMiniThumbButton_TextColor);
			textSize = dc.GetTextExtent( btnInst.m_pMiniButton->m_toolTip.GetBuffer() );
			rcTip.left = rcBtn.right + 1;
			rcTip.right = rcTip.left + textSize.cx + 10;
			rcTip.top = rcBtn.top;
			rcTip.bottom = rcBtn.bottom;
			dc.FillSolidRect(&rcTip, AssetViewer::kMiniThumbButton_BackColor);
			dc.SelectObject(GetStockObject(HOLLOW_BRUSH));
			pen.CreatePen(PS_SOLID, 1, AssetViewer::kMiniThumbButton_BorderColor);
			dc.SelectObject(pen);
			dc.Rectangle(&rcTip);
			pen.DeleteObject();
			dc.DrawText( btnInst.m_pMiniButton->m_toolTip.GetBuffer(), &rcTip, DT_SINGLELINE|DT_VCENTER|DT_CENTER );
			fntTip.DeleteObject();
		}
	}
}

void CAssetViewer::DrawCachingInfoProgressBar( CDC& dc )
{
	//
	// draw the info band which is saying that the assets are being cached for field info gathering
	//
	if( m_bCachingInProgress )
	{
		CRect rc;

		rc = m_oClientRect;
		rc.top = rc.bottom - AssetViewer::kCachingInfoProgressBar_Height;
		m_canvas.GradientFillRect( rc, RGB( 255, 55, 0 ), RGB( 70, 30, 0 ), GRADIENT_FILL_RECT_V );
		CRect rcShadowText = rc;
		CRect rcText = rc;
		rcShadowText.OffsetRect( 1, 1 );
		CFont fntInfo;

		fntInfo.CreatePointFont( 80, "Arial Bold" );
		dc.SelectObject( fntInfo );
		dc.SetTextColor( 0 );

		CString progressText;

		int cachingProgressPercent;

		if( !m_cachingCurrentCount && !m_cachingTotalCount )
		{
			cachingProgressPercent = 0;
		}
		else
		{
			cachingProgressPercent = 100.0f * (float)m_cachingCurrentCount / m_cachingTotalCount;
		}

		progressText.Format( "                         Caching of full asset information is in progress (%d%% %d/%d), filtering, sorting and searching will be limited at the moment", cachingProgressPercent, m_cachingCurrentCount, m_cachingTotalCount );

		rcShadowText.OffsetRect( 0, 4 );
		rcText.OffsetRect( 0, 4 );

		dc.DrawText( progressText, rcShadowText, DT_SINGLELINE | DT_WORD_ELLIPSIS );
		dc.SetTextColor( RGB( 255, 255, 255 ) );
		dc.DrawText( progressText, rcText, DT_SINGLELINE | DT_WORD_ELLIPSIS );
		dc.DrawIcon( rc.left + 4, rc.top + 2, m_hWarningSmallIcon );
		fntInfo.DeleteObject();

		// draw a little cancel button
		CRect cancelButtonRc = rc;
		cancelButtonRc.left = rc.left + 30;
		cancelButtonRc.top = rc.top + 2;
		cancelButtonRc.right = cancelButtonRc.left + 40;
		cancelButtonRc.bottom = cancelButtonRc.top + 18;
		m_cancelCachingButtonRect = cancelButtonRc;
		m_canvas.DrawBox( cancelButtonRc.left, cancelButtonRc.top, cancelButtonRc.Width(), cancelButtonRc.Height(), m_bCancelCachingButtonHovered ? RGB( 0, 0, 0 ) : RGB( 0, 155, 155 ), RGB( 0, 50, 50 ) );
		CFont btnFont;

		btnFont.CreatePointFont( 70, "Arial Bold" );
		dc.SelectObject( btnFont );
		dc.SetTextColor( RGB( 255, 255, 255 ) );
		dc.DrawText( "Cancel", cancelButtonRc, DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_VCENTER | DT_CENTER );
		btnFont.DeleteObject();

		// draw progress bar
		CRect progRc = rc;

		progRc.DeflateRect( 4, 4 );
		progRc.top = progRc.bottom - 4;
		progRc.OffsetRect( 0, 2 );
		dc.FillSolidRect( progRc, RGB( 255, 55, 0 ) );

		progRc.DeflateRect( 1, 1 );
		progRc.right = progRc.left + progRc.Width() * (float)cachingProgressPercent / 100.0f;
		dc.FillSolidRect( progRc, RGB( 255, 255, 0 ) );
	}
}

void CAssetViewer::DrawThumbnail( CDC& dc, IAssetDisplay* const poItem, const CRect& roUpdateRect )
{
	// if we have a thumbnail ready, draw it
	if( poItem->IsFlagSet( IAssetDisplay::eAssetFlags_ThumbnailCached ) )
	{
		if( !poItem->DrawThumbImage( dc.GetSafeHdc(), roUpdateRect ) )
		{
			m_canvas.GetDC().StretchBlt( roUpdateRect.left, roUpdateRect.top, roUpdateRect.Width(), roUpdateRect.Height(), &m_thumbLoadingBmp.GetDC(), 0, 0, m_thumbLoadingBmp.GetWidth(), m_thumbLoadingBmp.GetHeight(), SRCCOPY );
		}
	}
	else
	{
		if( poItem->IsFlagSet( IAssetDisplay::eAssetFlags_Invalid ) )
		{
			// draw a INVALID ASSET placeholder
			m_canvas.GetDC().StretchBlt( roUpdateRect.left, roUpdateRect.top, roUpdateRect.Width(), roUpdateRect.Height(), &m_thumbInvalidAssetBmp.GetDC(), 0, 0, m_thumbInvalidAssetBmp.GetWidth(), m_thumbInvalidAssetBmp.GetHeight(), SRCCOPY );
		}	
		else
		{
			// draw a LOADING ASSET placeholder
			m_canvas.GetDC().StretchBlt( roUpdateRect.left, roUpdateRect.top, roUpdateRect.Width(), roUpdateRect.Height(), &m_thumbLoadingBmp.GetDC(), 0, 0, m_thumbLoadingBmp.GetWidth(), m_thumbLoadingBmp.GetHeight(), SRCCOPY );
		}
	}
}

void CAssetViewer::DrawThumbMiniButtons( IAssetDisplay* const poItem, const CRect& rcThumbBorder )
{
	CString		strAssetTags;
	bool			bTagged = false, bFav = false;

	poItem->GetAssetFieldValue( "tags", &strAssetTags );
	poItem->GetAssetFieldValue( "favourite", &bFav );
	bTagged = !strAssetTags.IsEmpty();

	BLENDFUNCTION bfActive, bfHovered, bfInactive;

	m_canvas.MakeBlendFunc( AssetViewer::kMiniThumbButton_ActiveAlpha, bfActive );
	m_canvas.MakeBlendFunc( AssetViewer::kMiniThumbButton_HoveredAlpha, bfHovered );
	m_canvas.MakeBlendFunc( AssetViewer::kMiniThumbButton_InactiveAlpha, bfInactive );

	int crtY = rcThumbBorder.top;

	m_thumbMiniButtonInstances[poItem].resize( m_thumbMiniButtons.size() );

	for( size_t i = 0, iCount = m_thumbMiniButtons.size(); i < iCount; ++i )
	{
		CRect rcBtn;

		rcBtn.left = rcThumbBorder.right + 1;
		rcBtn.right = rcBtn.left + AssetViewer::kMiniThumbButton_Width;
		rcBtn.top = crtY;
		rcBtn.bottom = crtY + AssetViewer::kMiniThumbButton_Height;

		crtY += AssetViewer::kMiniThumbButton_Height + AssetViewer::kMiniThumbButton_VerticalSpacing;

		bool bOn = false;

		// check index to see what button is
		//TODO: put this bOn as a method of CThumbMiniButton and also the checking of asset tagged or fav
		bOn = bFav;

		BLENDFUNCTION* pBf = NULL;

		if( m_pHoveredThumbButtonAsset == poItem && m_nHoveredThumbButtonIndex == i )
		{
			pBf = &bfHovered;
		}
		else
			if( bOn )
			{
				pBf = &bfActive;
			}
			else
			{
				pBf = &bfInactive;
			}

			m_canvas.BitBltWithAlpha( m_thumbMiniButtons[i]->m_image.GetDC(), rcBtn.left, rcBtn.top, -1, -1, 0, 0, -1, -1, pBf );
			m_thumbMiniButtonInstances[poItem][i] = CThumbMiniButtonInstance( m_thumbMiniButtons[i], rcBtn, poItem, bOn );
	}
}

void CAssetViewer::EnqueueAssetForCaching( IAssetDisplay* poItem )
{
	// evict pending assets not seen anymore, no need to cache them
	TAssetItems::iterator iter = m_cPendingCacheAssets.begin();

	while( iter != m_cPendingCacheAssets.end() )
	{
		// if not anymore in drawing list, then take it out of pending cache list
		if( m_cAssetDrawingCache.end() == std::find( m_cAssetDrawingCache.begin(), m_cAssetDrawingCache.end(), *iter ) )
		{
			iter = m_cPendingCacheAssets.erase( iter );
			continue;
		}

		++iter;
	}

	// if not already in the pending list, then enqueue it
	if( m_cPendingCacheAssets.end() == std::find( m_cPendingCacheAssets.begin(), m_cPendingCacheAssets.end(), poItem ) )
	{
		m_cPendingCacheAssets.push_back( poItem );
		KillTimer( ID_ASSET_VIEWER_TIMER_CACHE_ASSET );
		SetTimer( ID_ASSET_VIEWER_TIMER_CACHE_ASSET, AssetViewer::kNonThreadCacheTimerDelay, NULL );
	}
}

bool CAssetViewer::CheckVirtualKey( int virtualKey )
{
	if( GetAsyncKeyState( virtualKey ) & ( 1 << 15 ) )
	{
		return true;
	}

	return false;
}

void CAssetViewer::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if( m_bMouseLeftButtonDown )
	{
		if( m_poClickedAsset &&
				m_poClickedAsset->IsFlagSet( IAssetDisplay::eAssetFlags_InteractiveRenderSupported ) )
		{
			m_poClickedAsset->OnInteractiveRenderKeyEvent( true, nChar, nFlags );
		}
	}
	else
	if( nChar == AssetViewer::kPreviewShortcutKey &&	( m_poHoveredAsset || !m_cSelectedAssets.empty() ) )
 	{
		// choose what item to preview: if we hover an asset, show that, if we dont hover any and have a selected asset, show that one instead
		IAssetDisplay* pAsset = m_poHoveredAsset ? m_poHoveredAsset : ( m_cSelectedAssets.empty() ? NULL : m_cSelectedAssets[0] );

		CALL_OBSERVERS_METHOD( OnChangedPreviewedAsset( pAsset ) );
	}

	if( m_bDraggingAssetFromBrowser || m_bDraggingCreatedAssetInWorld )
	{
		if( m_pDraggedAsset && m_pDraggedAssetInstance )
		{
			m_pDraggedAsset->AbortCreateInstanceInViewport( m_pDraggedAssetInstance );
		}

		// end of dragging, clear up stuff
		m_pDraggedAssetInstance = NULL;
		m_pDraggedAsset = NULL;
		m_bDraggingCreatedAssetInWorld = false;
		m_bDraggingAssetFromBrowser = false;
		m_pDropInViewport = NULL;
		::SetCursor( LoadCursor( NULL, IDC_ARROW ) );
		ReleaseCapture();
	}

	//
	// navigate using the ARROW KEYS and PGUP PGDOWN HOME END
	//
	if( m_cAssetItems.size() )
	{
		IAssetDisplay* pAsset = NULL;
		bool bHasSelection = !m_cSelectedAssets.empty(), bEpicFail = false;

		if( bHasSelection )
		{
			pAsset = m_cSelectedAssets[0];
		}
		else
		{
			pAsset = m_cAssetItems[0];
		}

		int index = 0;

		// search asset's index
		if( pAsset )
		{
			index = pAsset->GetIndex();
		}

		switch( nChar )
		{
			case VK_DOWN:
			{
				index += m_nThumbsPerRow;

				if( index < m_cAssetItems.size() )
				{
					SelectAsset( m_cAssetItems[index] );
					EnsureAssetVisible( index );
				}
				else
				{
					bEpicFail = true;
				}

				break;
			}

			case VK_UP:
			{
				index -= m_nThumbsPerRow;

				if( index >= 0 )
				{
					SelectAsset( m_cAssetItems[index] );
					EnsureAssetVisible( index );
				}
				else
				{
					bEpicFail = true;
				}

				break;
			}
			
			case VK_LEFT:
			{
				--index;

				if( index < 0 )
				{
					index = 0;
				}

				SelectAsset( m_cAssetItems[index] );
				EnsureAssetVisible( index );
				break;
			}
			
			case VK_RIGHT:
			{
				++index;

				if( index >= m_cAssetItems.size() )
				{
					index = m_cAssetItems.size() - 1;
				}

				SelectAsset( m_cAssetItems[index] );
				EnsureAssetVisible( index );
				break;
			}
			
			case VK_NEXT:
			{
				index += m_nThumbsPerRow*m_nVisibleThumbRows;

				if( index < m_cAssetItems.size() )
				{
					SelectAsset( m_cAssetItems[index] );
					EnsureAssetVisible( index );
				}
				else
				{
					SelectAsset( m_cAssetItems[m_cAssetItems.size() - 1] );
					EnsureAssetVisible( m_cAssetItems.size() - 1 );
				}

				break;
			}
			
			case VK_PRIOR:
			{
				index -= m_nThumbsPerRow*m_nVisibleThumbRows;

				if( index >= 0 )
				{
					SelectAsset( m_cAssetItems[index] );
					EnsureAssetVisible( index );
				}
				else
				{
					SelectAsset( m_cAssetItems[0] );
					EnsureAssetVisible( m_cAssetItems[0] );
				}

				break;
			}
			
			case VK_HOME:
			{
					SelectAsset( m_cAssetItems[0] );
					EnsureAssetVisible( m_cAssetItems[0] );
					break;
			}
			
			case VK_END:
			{
				SelectAsset( m_cAssetItems[m_cAssetItems.size() - 1] );
				EnsureAssetVisible( m_cAssetItems.size() - 1 );
				break;
			}
		}

		// if we couldnt advance, then "failback" to previous selection
		if( bHasSelection && bEpicFail && pAsset )
		{
			// just set the old selected asset back
			SelectAsset( pAsset );
			EnsureAssetVisible( pAsset );
		}
	}
	__super::OnKeyDown( nChar, nRepCnt, nFlags );
}

void CAssetViewer::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if( m_bMouseLeftButtonDown )
	{
		if( m_poClickedAsset &&
			m_poClickedAsset->IsFlagSet( IAssetDisplay::eAssetFlags_InteractiveRenderSupported ) )
		{
			m_poClickedAsset->OnInteractiveRenderKeyEvent( false, nChar, nFlags );
		}
	}
	__super::OnKeyUp(nChar, nRepCnt, nFlags);
}

void CAssetViewer::RefreshDatabases()
{
	if( !CAssetMetaDataFileDB::IsLoadAndUpdateDone() )
	{
		return;
	}

	if( m_bCachingInProgress )
	{
		return;
	}

	if(IsStarted() || IsRunning())
	{			
		Stop();
		WaitForThread();
	}

	DeselectAll();
	LockAll();
	m_poClickedAsset = NULL;
	m_cAssetDrawingCache.clear();
	m_cPendingCacheAssets.clear();
	m_cSelectedAssets.clear();
	m_cThumbsCache.clear();
	m_poEnsureVisible = NULL;
	m_poClickedAsset = NULL;
	m_poHoveredAsset = NULL;
	m_pDraggedAsset = NULL;
	m_pDropInViewport = NULL;
	m_pDraggedAssetInstance = NULL;
	m_thumbMiniButtonInstances.clear();
	m_cAssetItems.clear();

	for( size_t i = 0, iCount = m_cAssetDatabases.size(); i < iCount; ++i )
	{
		m_cAssetDatabases[i]->Refresh();
		m_nTotalAssets += m_cAssetDatabases[i]->GetAssets().size();
	}
	
	m_cAssetItems.reserve( m_nTotalAssets );

	for( size_t i = 0, iCount = m_cAssetDatabases.size(); i < iCount; ++i )
	{
		IAssetDisplayDatabase::TFilenameAssetMap& rAssets = m_cAssetDatabases[i]->GetAssets();

		m_cAssetDatabases[i]->CacheFieldsInfoForAlreadyLoadedAssets();

		// add items in the list if not already added
		for( IAssetDisplayDatabase::TFilenameAssetMap::iterator iter = rAssets.begin(), iterEnd = rAssets.end(); iter != iterEnd; ++iter )
		{
			// skip this one, is filtered out
			if( !iter->second->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
				continue;

			iter->second->SetIndex( m_cAssetItems.size() );
			m_cAssetItems.push_back( iter->second );
		}
	}

	RecalculateLayout();
	UnlockAll();

	RefreshAssetsUsedInLevel();

	UpdateScrollBar();
	UpdateVisibility();
	CALL_OBSERVERS_METHOD( OnChangeStatusBarInfo( m_cSelectedAssets.size(), m_cAssetItems.size(), m_nTotalAssets ) );
	CALL_OBSERVERS_METHOD( OnSelectionChanged() );
	RestartThread();
}

void CAssetViewer::RefreshAssetsUsedInLevel()
{
	m_usedAssetsInLevel.clear();
	CFileUtil::GatherAssetFilenamesFromLevel( m_usedAssetsInLevel, true );

	LockAll();

	for( size_t i = 0, iCount = m_cAssetDatabases.size(); i < iCount; ++i )
	{
		IAssetDisplayDatabase::TFilenameAssetMap& rAssets = m_cAssetDatabases[i]->GetAssets();
		
		for( IAssetDisplayDatabase::TFilenameAssetMap::iterator iter = rAssets.begin(); iter != rAssets.end(); ++iter )
		{
			bool bIsUsed = ( m_usedAssetsInLevel.end() != std::find( m_usedAssetsInLevel.begin(), m_usedAssetsInLevel.end(), iter->first ) );
			
			iter->second->SetFlag( IAssetDisplay::eAssetFlags_UsedInLevel, bIsUsed );
		}
	}

	UnlockAll();
}

void CAssetViewer::RecalculateTotalAssetCount()
{
	LockAll();

	m_nTotalAssets = 0;

	for( size_t i = 0, iCount = m_cAssetDatabases.size(); i < iCount; ++i )
	{
		IAssetDisplayDatabase::TFilenameAssetMap& rAssets = m_cAssetDatabases[i]->GetAssets();
		m_nTotalAssets += rAssets.size();
	}

	UnlockAll();
}

void CAssetViewer::CachingStarted()
{
	m_bCachingInProgress = true;
	m_cachingCurrentCount = 0;
	m_cachingTotalCount = 0;
	m_bCancelCachingButtonPressed = false;
}

void CAssetViewer::CachingEnded()
{
	m_bCachingInProgress = false;
	m_cachingCurrentCount = 0;
	m_cachingTotalCount = 0;
}

void CAssetViewer::SetCachingProgress( int aCachingCurrentCount, int aCachingTotalCount )
{
	m_cachingCurrentCount = aCachingCurrentCount;
	m_cachingTotalCount = aCachingTotalCount;
}

bool CAssetViewer::IsCachingInProgress()
{
	return m_bCachingInProgress;
}

bool CAssetViewer::IsCachingCancelled()
{
	return m_bCancelCachingButtonPressed;
}

HWND CAssetViewer::GetRenderWindow()
{
	return m_wndAssetThumbRender.GetSafeHwnd();
}

TAssetItems& CAssetViewer::GetAssetItems()
{
	return m_cAssetItems;
}

bool CAssetViewer::IsAssetVisibleInView( IAssetDisplay* pAsset )
{
	assert(pAsset);

	if( !pAsset )
		return false;

	if( !pAsset->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
	{
		return false;
	}

	CRect oCurrentRect, oIntersection;

	pAsset->GetDrawingRectangle( oCurrentRect );
	oCurrentRect.top -= m_nYOffset;
	oCurrentRect.bottom -= m_nYOffset;

	// If the item is entirely outside of the client rect, no need to cache a bitmap for it.
	if( oCurrentRect.bottom < 0 )
	{
		return false;
	}

	// If the item is entirely below the client rect, as Y coordinates grow monotonically,
	// no need to cache anything else.
	if( oCurrentRect.top > m_oClientRect.bottom )
	{
		return false;
	}

	// Added to use the same criteria as in the remove list.
	if( !oIntersection.IntersectRect( m_oClientRect, oCurrentRect ) )
	{
		return false;
	}

	return true;
}

void CAssetViewer::PushToThumbsCacheQueue( IAssetDisplay* pAsset )
{
	if( !m_cThumbsCache.empty() )
	{
		// first check if this is already in the cache
		if( std::find( m_cThumbsCache.begin(), m_cThumbsCache.end(), pAsset ) != m_cThumbsCache.end() )
		{
			return;
		}
	}

	// if we have a filled up queue, then uncache oldest thumb
	if( m_cThumbsCache.size() >= AssetViewer::kMaxThumbsCacheAssetCount )
	{
		IAssetDisplay* pOldest = m_cThumbsCache.front();
		
		pOldest->UnCacheThumbnail();
		m_cThumbsCache.erase(	m_cThumbsCache.begin() );
	}

	m_cThumbsCache.push_back( pAsset );
}

CString CAssetViewer::GetAssetFieldDisplayValue( IAssetDisplay* pAsset, const char* pFieldname )
{
	assert( pAsset );
	assert( pAsset->GetOwnerDisplayDatabase() );

	SAssetField* pField = pAsset->GetOwnerDisplayDatabase()->GetAssetFieldByName( pFieldname );

	if( !pField )
		return "";

	switch( pField->m_fieldType )
	{
		case SAssetField::eAssetFieldType_Bool:
		{
			bool value;

			if( pAsset->GetAssetFieldValue( pFieldname, &value ) )
			{
				return value ? "Yes" : "No";
			}

			break;
		}

		case SAssetField::eAssetFieldType_Int8:
		{
			char value;

			if( pAsset->GetAssetFieldValue( pFieldname, &value ) )
			{
				stack_string str;
				str.Format( "%d", value );
				return str.c_str();
			}

			break;
		}
	
		case SAssetField::eAssetFieldType_Int16:
		{
			short int value;

			if( pAsset->GetAssetFieldValue( pFieldname, &value ) )
			{
				stack_string str;
				str.Format( "%d", value );
				return str.c_str();
			}

			break;
		}

		case SAssetField::eAssetFieldType_Int32:
		{
			int value;

			if( pAsset->GetAssetFieldValue( pFieldname, &value ) )
			{
				stack_string str;
				str.Format( "%d", value );
				return str.c_str();
			}

			break;
		}

		case SAssetField::eAssetFieldType_Int64:
		{
			__int64 value;

			if( pAsset->GetAssetFieldValue( pFieldname, &value ) )
			{
				stack_string str;
				str.Format( "%I64", value );
				return str.c_str();
			}

			break;
		}

		case SAssetField::eAssetFieldType_Float:
		{
			float value;
			
			if( pAsset->GetAssetFieldValue( pFieldname, &value ) )
			{
				stack_string str;
				str.Format( "%.4f", value );
				return str.c_str();
			}

			break;
		}

		case SAssetField::eAssetFieldType_Double:
		{
			double value;
			
			if( pAsset->GetAssetFieldValue( pFieldname, &value ) )
			{
				stack_string str;
				str.Format( "%g", value );
				return str.c_str();
			}

			break;
		}

		case SAssetField::eAssetFieldType_String:
		{
			CString value;
			
			if( pAsset->GetAssetFieldValue( pFieldname, &value ) )
			{
				return value;
			}
		}
	}

	return "";
}

//------------------------------------------------------------------------------
// SORTING ASSETS
//------------------------------------------------------------------------------

bool CAssetViewer::SortAssetItems( IAssetDisplay* pA, IAssetDisplay* pB )
{
	assert( pA );
	assert( pB );

	if( !pA || !pB )
		return false;

	switch( CAssetViewer::s_pSortField->m_fieldType )
	{
		case SAssetField::eAssetFieldType_Bool:
		{
			return CompareAssetFieldsValues<bool>( pA, pB );
		}

		case SAssetField::eAssetFieldType_Int8:
		{
			return CompareAssetFieldsValues<char>( pA, pB );
		}

		case SAssetField::eAssetFieldType_Int16:
		{
			return CompareAssetFieldsValues<short int>( pA, pB );
		}

		case SAssetField::eAssetFieldType_Int32:
		{
			return CompareAssetFieldsValues<int>( pA, pB );
		}

		case SAssetField::eAssetFieldType_Int64:
		{
			return CompareAssetFieldsValues<__int64>( pA, pB );
		}

		case SAssetField::eAssetFieldType_Float:
		{
			return CompareAssetFieldsValues<float>( pA, pB );
		}

		case SAssetField::eAssetFieldType_Double:
		{
			return CompareAssetFieldsValues<double>( pA, pB );
		}

		case SAssetField::eAssetFieldType_String:
		{
			return CompareAssetFieldsValues<CString>( pA, pB );
		}
	}

	return false;
}

void CAssetViewer::SortAssets( const char* pFieldname, bool bDescending )
{
	LockAll();

	if( m_cAssetItems.empty() )
	{
		UnlockAll();
		return;
	}

	m_strCurrentSortFieldname = pFieldname;
	CAssetViewer::s_bSortDescending = bDescending;
	CAssetViewer::s_pSortField = m_cAssetItems[0]->GetOwnerDisplayDatabase()->GetAssetFieldByName( pFieldname );

	if( CAssetViewer::s_pSortField )
	{
		std::sort( m_cAssetItems.begin(), m_cAssetItems.end(), SortAssetItems );
	}

	UnlockAll();
	RecalculateLayout();
	UpdateScrollBar();
	UpdateVisibility();
	CALL_OBSERVERS_METHOD( OnChangeStatusBarInfo( m_cSelectedAssets.size(), m_cAssetItems.size(), m_nTotalAssets ) );
	CALL_OBSERVERS_METHOD( OnSelectionChanged() );
}

void CAssetViewer::ApplyFilters( const IAssetDisplayDatabase::TAssetFieldFiltersMap& rFieldFilters )
{
	LockAll();

	for( size_t i = 0, iCount = m_cAssetDatabases.size(); i < iCount; ++i )
	{
		m_cAssetDatabases[i]->ApplyFilters( rFieldFilters );
	}

	m_cAssetItems.clear();
	m_cThumbsCache.clear();
	m_cAssetDrawingCache.clear();

	for( size_t i = 0, iCount = m_cAssetDatabases.size(); i < iCount; ++i )
	{
		IAssetDisplayDatabase::TFilenameAssetMap& rAssets = m_cAssetDatabases[i]->GetAssets();

		// add items in the list if not already added
		for( IAssetDisplayDatabase::TFilenameAssetMap::iterator iter = rAssets.begin(), iterEnd = rAssets.end(); iter != iterEnd; ++iter )
		{
			// skip this one, is filtered out
			if( !iter->second->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
				continue;

			iter->second->SetIndex( m_cAssetItems.size() );
			m_cAssetItems.push_back( iter->second );
		}
	}

	UnlockAll();
	m_nYOffset = 0;
	RecalculateLayout();
	UpdateScrollBar();
	UpdateVisibility();
	CALL_OBSERVERS_METHOD( OnChangeStatusBarInfo( m_cSelectedAssets.size(), m_cAssetItems.size(), m_nTotalAssets ) );
	CALL_OBSERVERS_METHOD( OnSelectionChanged() );
}

void CAssetViewer::ClearFilters()
{
	LockAll();

	for( size_t i = 0, iCount = m_cAssetDatabases.size(); i < iCount; ++i )
	{
		m_cAssetDatabases[i]->ClearFilters();
	}

	m_cAssetItems.clear();
	m_cThumbsCache.clear();
	m_cAssetDrawingCache.clear();

	for( size_t i = 0, iCount = m_cAssetDatabases.size(); i < iCount; ++i )
	{
		IAssetDisplayDatabase::TFilenameAssetMap& rAssets = m_cAssetDatabases[i]->GetAssets();

		// add items in the list if not already added
		for( IAssetDisplayDatabase::TFilenameAssetMap::iterator iter = rAssets.begin(), iterEnd = rAssets.end(); iter != iterEnd; ++iter )
		{
			// skip this one, is filtered out
			if( !iter->second->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
				continue;

			iter->second->SetIndex( m_cAssetItems.size() );
			m_cAssetItems.push_back( iter->second );
		}
	}

	UnlockAll();
	m_nYOffset = 0;
	RecalculateLayout();
	UpdateScrollBar();
	UpdateVisibility();
	CALL_OBSERVERS_METHOD( OnChangeStatusBarInfo( m_cSelectedAssets.size(), m_cAssetItems.size(), m_nTotalAssets ) );
	CALL_OBSERVERS_METHOD( OnSelectionChanged() );
}

void CAssetViewer::EnsureAssetVisible( UINT aIndex, bool bPlayFocusAnimation )
{
	if( aIndex >= m_cAssetItems.size() )
	{
		m_poEnsureVisible = NULL;
		return;
	}

	m_poEnsureVisible = m_cAssetItems[aIndex];
	EnsureAssetVisible( m_poEnsureVisible, bPlayFocusAnimation );
}

void CAssetViewer::EnsureAssetVisible( IAssetDisplay* pAsset, bool bPlayFocusAnimation )
{
	m_poEnsureVisible = pAsset;
	m_smoothPanLastDelta = 0;
	m_smoothPanLeftAmount = 0;

	if( m_poEnsureVisible )
	{
		m_poEnsureVisible->SetFlag( IAssetDisplay::eAssetFlags_Selected, true );

		if( m_poEnsureVisible->IsFlagSet( IAssetDisplay::eAssetFlags_Visible ) )
		{
			CRect oElementRect, oDrawRect;

			m_poEnsureVisible->GetDrawingRectangle( oElementRect );
			oDrawRect = oElementRect;
			oDrawRect.OffsetRect( 0, -m_nYOffset );

			if( oDrawRect.top < 0 || oDrawRect.bottom > m_oClientRect.Height() )
			{
				m_nYOffset = oElementRect.top - m_nItemVerticalSpacing;
			}
		}
	}

	UpdateScrollBar();
	UpdateVisibility();
	
	if( bPlayFocusAnimation )
	{
		m_thumbFocusAnimationTime = 0;
		KillTimer( ID_ASSET_VIEWER_TIMER_THUMB_FOCUS_ANIMATION );
		SetTimer( ID_ASSET_VIEWER_TIMER_THUMB_FOCUS_ANIMATION, AssetViewer::kThumbFocusAnimationTimerDelay, NULL );
	}

	Invalidate( FALSE );
}

bool CAssetViewer::IsThumbViewportInteraction()
{
	return m_poClickedAsset && m_bDragging && ( m_bMouseLeftButtonDown || m_bMouseRightButtonDown || m_bMouseMiddleButtonDown );
}

BOOL CAssetViewer::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	if( m_bDraggingAssetFromBrowser )
	{
		::SetCursor( LoadCursor( AfxGetApp()->m_hInstance, MAKEINTRESOURCE( IDC_POINTER_DRAG_ITEM ) ) );
		return TRUE;
	}
	else
	if( m_pHoveredThumbButtonAsset && m_nHoveredThumbButtonIndex >= 0 )
	{
		::SetCursor( LoadCursor( AfxGetApp()->m_hInstance, MAKEINTRESOURCE( IDC_CURSOR_HAND_FINGER ) ) );
		return TRUE;
	}

	return __super::OnSetCursor(pWnd, nHitTest, message);
}

